web-dev-qa-db-ja.com

RecyclerViewが期待どおりにスクロールしない

水平リサイクラビューを使用するプロジェクトがあり、1つの要素を中央に配置したい。私の実装は機能しますが、すべてのケースでこのGIFをチェックするわけではありません。

お気づきかもしれませんが、左から来ると正しくスクロールします。私が右から来た場合、それは大きくスクロールし、停止する方法も修正する方法もわかりません。

ここでこの例のコードをストライプしました。

public class DemoActivity extends ActionBarActivity implements View.OnClickListener {
    private static final int JUMP_TO_LEFT = MyAdapter.NON_VISIBLE_ITEMS + MyAdapter.VISIBLE_ITEMS - 1;
    private static final int JUMP_TO_RIGHT = MyAdapter.NON_VISIBLE_ITEMS;
    private LinearLayoutManager mLayoutManager;
    private RecyclerView mRecycler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);
        findViewById(Android.R.id.button1).setOnClickListener(this);
        mRecycler = (RecyclerView)findViewById(R.id.recycler);
        MyAdapter mAdapter = new MyAdapter();
        mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
        mRecycler.setLayoutManager(mLayoutManager);
        mRecycler.setHasFixedSize(true);
        mRecycler.scrollToPosition(MyAdapter.NON_VISIBLE_ITEMS);
        mRecycler.setAdapter(mAdapter);
    }

    @Override
    public void onClick(View v) {
        int pos = mLayoutManager.findFirstVisibleItemPosition();
        int outer = (MyAdapter.VISIBLE_ITEMS - 1) / 2;
        if(pos + outer >= MyAdapter.ITEM_IN_CENTER) {
            mRecycler.smoothScrollToPosition(JUMP_TO_RIGHT);
        } else {
            mRecycler.smoothScrollToPosition(JUMP_TO_LEFT);
        }
    }
}

そして、ここに私のアダプターがあります:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.Holder> implements View.OnClickListener {
    public static final int VISIBLE_ITEMS = 7;
    public static final int NON_VISIBLE_ITEMS = 150;
    private static final int TOTAL_ITEMS = VISIBLE_ITEMS + NON_VISIBLE_ITEMS * 2;
    public static final int ITEM_IN_CENTER = (int)Math.ceil(VISIBLE_ITEMS / 2f) + NON_VISIBLE_ITEMS;

    private Calendar mCalendar;

    public MyAdapter() {
        mCalendar = GregorianCalendar.getInstance();
        setHasStableIds(true);
    }

    private int getToday() {
        return (int)TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
    }

    @Override
    public int getItemCount() {
        return TOTAL_ITEMS;
    }

    @Override
    public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
        final TextView tv = new TextView(parent.getContext());
        int width = parent.getWidth() / VISIBLE_ITEMS;
        tv.setLayoutParams(new TableRow.LayoutParams(width, ViewGroup.LayoutParams.MATCH_PARENT, 1));
        tv.setGravity(Gravity.CENTER);
        tv.setBackgroundColor(Color.TRANSPARENT);
        DisplayMetrics metrics = tv.getContext().getResources().getDisplayMetrics();
        float padding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, metrics);
        tv.setLineSpacing(padding, 1f);
        tv.setPadding(0, (int)padding, 0, 0);
        tv.setOnClickListener(this);
        return new Holder(tv);
    }

    @Override
    public void onBindViewHolder(Holder holder, int position) {
        int today = getToday();
        mCalendar.setTimeInMillis(System.currentTimeMillis());
        mCalendar.set(Calendar.HOUR_OF_DAY, 12); // set to noon to avoid energy saver time problems
        mCalendar.add(Calendar.DAY_OF_YEAR, position - ITEM_IN_CENTER + 1);
        DateFormat format = new SimpleDateFormat("E\nd");
        String label = format.format(mCalendar.getTime()).replace(".\n", "\n");
        int day = (int)TimeUnit.MILLISECONDS.toDays(mCalendar.getTimeInMillis());
        holder.update(day, today, label);
    }

    @Override
    public long getItemId(int position) {
        mCalendar.setTimeInMillis(System.currentTimeMillis());
        mCalendar.set(Calendar.HOUR_OF_DAY, 12); // set to noon to avoid energy saver time problems
        mCalendar.add(Calendar.DAY_OF_YEAR, position - ITEM_IN_CENTER + 1);
        DateFormat format = new SimpleDateFormat("dMMyyyy");
        return Long.parseLong(format.format(mCalendar.getTime()));
    }

    @Override
    public void onClick(View v) {
        String day = ((TextView)v).getText().toString().replace("\n", " ");
        Toast.makeText(v.getContext(), "You clicked on " + day, Toast.LENGTH_SHORT).show();
    }

    public class Holder extends RecyclerView.ViewHolder {
        private final Typeface font;

        private Holder(TextView v) {
            super(v);
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                font = Typeface.create("sans-serif-light", Typeface.NORMAL);
            } else {
                font = null;
            }
        }

        public void update(int day, int today, String label) {
            TextView tv = (TextView)itemView;
            tv.setText(label);

            if(day == today) {
                tv.setTextSize(18);
                tv.setTypeface(null, Typeface.BOLD);
            } else {
                tv.setTextSize(16);
                tv.setTypeface(font, Typeface.NORMAL);
            }

            tv.setBackgroundColor(0xff8dc380);
        }
    }
}

その理由がわかりますか?より簡単にするために、このコードをGitHubにも配置しました。 https://github.com/rekire/RecylcerViewBug

28
rekire

私は驚くべき簡単な回避策を見つけました:

@Override
public void onClick(View v) {
    int pos = mLayoutManager.findFirstVisibleItemPosition();
    int outer = (MyAdapter.VISIBLE_ITEMS + 1) / 2;
    int delta = pos + outer - ForecastAdapter.ITEM_IN_CENTER;
    //Log.d("Scroll", "delta=" + delta);
    View firstChild = mForecast.getChildAt(0);
    if(firstChild != null) {
        mForecast.smoothScrollBy(firstChild.getWidth() * -delta, 0);
    }
}

ここで、自分でジャンプする幅を計算します。

14
rekire

垂直方向のLinearLayoutManagerの場合、独自のSmoothScrollerを作成し、calculateDyToMakeVisible()メソッドをオーバーライドして、目的のビュー位置を設定できます。たとえば、smoothScroll()の後にターゲットビューを常にRecyclerViewの上部に表示するには、次のように記述します。

class CustomLinearSmoothScroller extends LinearSmoothScroller {

    public CustomLinearSmoothScroller(Context context) {
        super(context);
    }

    @Override
    public int calculateDyToMakeVisible(View view, int snapPreference) {
        final RecyclerView.LayoutManager layoutManager = getLayoutManager();
        if (!layoutManager.canScrollVertically()) {
            return 0;
        }
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)view.getLayoutParams();
        final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
        final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
        final int viewHeight = bottom - top;
        final int start = layoutManager.getPaddingTop();
        final int end = start + viewHeight;
        return calculateDtToFit(top, bottom, start, end, snapPreference);
    }

「top」および「bottom」-ターゲットビューの境界

「開始」と「終了」-smoothScrollの間にビューを配置するポイント

5
forcelain

スムーズスクロールをサポートするには、smoothScrollToPosition(RecyclerView、State、int)をオーバーライドして、RecyclerView.SmoothScrollerを作成する必要があります。

RecyclerView.LayoutManagerは、実際のスクロールアクションを作成します。カスタムのスムーズスクロールロジックを提供する場合は、LayoutManagerでsmoothScrollToPosition(RecyclerView、State、int)をオーバーライドします。

https://developer.Android.com/reference/Android/support/v7/widget/RecyclerView.html#smoothScrollToPosition(int)

あなたの場合、smoothScrollByを使用することで回避できます(このオーバーライドは必要ありません)。

0
stankocken

これは私のために働いた:

itemsView.smoothScrollBy(-recyclerView.computeHorizontalScrollOffset(), 0)
0
Ixx