RecyclerView庫中的遺珠

RecyclerView的強大無人不知,它封裝了ViewHolder,便於我們回收複用;配合LayoutManagerItemDecorationItemAnimator 便於你制定各種列表效果。當然可能還有一些“遺珠”你不太瞭解,今天就說說它們。

1.SortedList

顧名思義就是排序列表,它適用於列表有序且不重複的場景。並且SortedList會幫助你比較數據的差異,定向刷新數據。而不是簡單粗暴的notifyDataSetChanged()

我想到了一個場景,在選擇城市頁面,我們都需要根據拼音首字母來排序。我們來使用SortedList來實現一下。

City對象:

public class City {
    
    private int id;
    private String cityName;
    private String firstLetter;

    public City(int id, String cityName, String firstLetter) {
        this.id = id;
        this.cityName = cityName;
        this.firstLetter = firstLetter;
    }
}

創建SortedListAdapterCallback 的實現類 SortedListCallbackSortedListCallback 定義瞭如何排序和如何判斷重複項。

public class SortedListCallback extends SortedListAdapterCallback<City> {
    
    public SortedListCallback(RecyclerView.Adapter adapter) {
        super(adapter);
    }

    /**
     * 排序條件
     */
    @Override
    public int compare(City o1, City o2) {
        return o1.getFirstLetter().compareTo(o2.getFirstLetter());
    }

    /**
     * 用來判斷兩個對象是否是相同的Item。
     */
    @Override
    public boolean areItemsTheSame(City item1, City item2) {
        return item1.getId() == item2.getId();
    }
    
    /**
     * 用來判斷兩個對象是否是內容的Item。
     */
    @Override
    public boolean areContentsTheSame(City oldItem, City newItem) {
        if (oldItem.getId() != newItem.getId()) {
            return false;
        }
        return oldItem.getCityName().equals(newItem.getCityName());
    }
}

Adapter部分

public class SortedAdapter extends RecyclerView.Adapter<SortedAdapter.ViewHolder> {
   
    // 數據源使用SortedList
    private SortedList<City> mSortedList;
    private LayoutInflater mInflater;
    
    public SortedAdapter(Context mContext) {
        mInflater = LayoutInflater.from(mContext);
    }

    public void setSortedList(SortedList<City> mSortedList) {
        this.mSortedList = mSortedList;
    }
    
    /**
     * 批量更新操作,例如:
     * <pre>
     *     mSortedList.beginBatchedUpdates();
     *     try {
     *         mSortedList.add(item1)
     *         mSortedList.add(item2)
     *         mSortedList.remove(item3)
     *         ...
     *     } finally {
     *         mSortedList.endBatchedUpdates();
     *     }
     * </pre>
    * */
    public void setData(List<City> mData){
        mSortedList.beginBatchedUpdates();
        mSortedList.addAll(mData);
        mSortedList.endBatchedUpdates();
    }

    public void removeData(int index){
        mSortedList.removeItemAt(index);
    }

	public void clear(){
        mSortedList.clear();
    }
    
    @Override
    @NonNull
    public SortedAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull SortedAdapter.ViewHolder holder, final int position) {
       ...
    }
    
    @Override
    public int getItemCount() {
        return mSortedList.size();
    }

    ...
}

使用部分:

public class SortedListActivity extends AppCompatActivity {

    private SortedAdapter mSortedAdapter;
    private int count = 10;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sorted_list);
        RecyclerView mRecyclerView = findViewById(R.id.rv);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mSortedAdapter = new SortedAdapter(this);
        // SortedList初始化
        SortedListCallback mSortedListCallback = new SortedListCallback(mSortedAdapter);
        SortedList mSortedList = new SortedList<>(City.class, mSortedListCallback);
        
        mSortedAdapter.setSortedList(mSortedList);
        mRecyclerView.setAdapter(mSortedAdapter);
        updateData();
    }

    private void addData() {
        mSortedAdapter.setData(new City(count, "城市 " + count, "c"));
        count ++;
    }

    private List<City> mList = new ArrayList();

    private void updateData() {
        mList.clear();
        mList.add(new City(0, "北京", "b"));
        mList.add(new City(1, "上海", "s"));
        mList.add(new City(2, "廣州", "g"));
        mList.add(new City(3, "深圳", "s"));
        mList.add(new City(4, "杭州", "h"));
        mList.add(new City(5, "西安", "x"));
        mList.add(new City(6, "成都", "c"));
        mList.add(new City(7, "武漢", "w"));
        mList.add(new City(8, "南京", "n"));
        mList.add(new City(9, "重慶", "c"));
        mSortedAdapter.setData(mList);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
        return true;
    }
    
    private Random mRandom = new Random();

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int i = item.getItemId();
        if (i == R.id.menu_add) {
            addData();
        } else if (i == R.id.menu_update) {
        	// 修改,自動去重
            updateData();
        } else if (i == R.id.menu_delete) {
        	// 隨意刪除一個
            if (mSortedAdapter.getItemCount() > 0){
                mSortedAdapter.removeData(mRandom.nextInt(mSortedAdapter.getItemCount()));
            }
        }else if (i == R.id.menu_clear){
            mSortedAdapter.clear();
        }
        return true;
    }
}

使用起來還是很簡單的,來看一下效果圖:

在這裏插入圖片描述

可以看到,我每次添加一條c字母的數據,它會自動幫我排序好,同時刷新列表。修改數據時,自動去重。比起暴力刷新,優雅多了。

2. AsyncListUtil

AsyncListUtil 在 support-v7:23就存在了。它是異步加載數據的工具,它一般用於加載數據庫數據,我們無需在UI線程上查詢遊標,同時它可以保持UI和緩存同步,並且始終只在內存中保留有限數量的數據。使用它可以獲得更好的用戶體驗。

注意,這個類使用單個線程來加載數據,因此它適合從磁盤、數據庫加載數據,不適用於從網絡加載數據。

用法如下,首先實現AsyncListUtil

public class MyAsyncListUtil extends AsyncListUtil<TestBean> {

    /**
     * 一次加載數據的個數,分頁數量
     */
    private static final int TILE_SIZE = 20;

    public MyAsyncListUtil(RecyclerView mRecyclerView) {
        super(TestBean.class, TILE_SIZE, new MyDataCallback(), new MyViewCallback(mRecyclerView));

        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                // 更新當前可見範圍數據
                onRangeChanged();
            }
        });
    }

    /**
     * 獲取數據回調
     */
    public static class MyDataCallback extends DataCallback<TestBean>{

        /**
         * 總數據個數
         */
        @Override
        public int refreshData() {
            return 200;
        }

        /**
         * 填充數據(後臺線程),一般爲讀取數據庫數據
         */
        @Override
        public void fillData(@NonNull TestBean[] data, int startPosition, int itemCount) {
            for (int i = 0; i < itemCount; i++) {
                TestBean item = data[i];
                if (item == null) {
                    item = new TestBean(startPosition, "Item:" + (startPosition + i));
                    data[i] = item;
                }
            }

            try {
                // 模擬加載數據中
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 用於獲取可見項範圍和更新通知的回調
     */
    public static class MyViewCallback extends ViewCallback {

        private RecyclerView mRecyclerView;

        public MyViewCallback(RecyclerView mRecyclerView) {
            this.mRecyclerView = mRecyclerView;
        }

        /**
         * 展示數據的範圍
         */
        @Override
        public void getItemRangeInto(@NonNull int[] outRange) {
            RecyclerView.LayoutManager manager = mRecyclerView.getLayoutManager();
            LinearLayoutManager mgr = (LinearLayoutManager) manager;
            outRange[0] = mgr.findFirstVisibleItemPosition();
            outRange[1] = mgr.findLastVisibleItemPosition();
        }

        /**
         * 刷新數據
         */
        @Override
        public void onDataRefresh() {
            mRecyclerView.getAdapter().notifyDataSetChanged();
        }

        /**
         * Item更新
         */
        @Override
        public void onItemLoaded(int position) {
            mRecyclerView.getAdapter().notifyItemChanged(position);
        }
    }
}

Adapter:

public class AsyncListUtilAdapter extends RecyclerView.Adapter<AsyncListUtilAdapter.ViewHolder> {
   
    private MyAsyncListUtil mMyAsyncListUtil;
    
    public AsyncListUtilAdapter(Context mContext, MyAsyncListUtil mMyAsyncListUtil) {
        this.mMyAsyncListUtil = mMyAsyncListUtil;
    }

    @Override
    public int getItemCount() {
        return mMyAsyncListUtil.getItemCount();
    }

    @Override
    public void onBindViewHolder(@NonNull AsyncListUtilAdapter.ViewHolder holder, final int position) {
        TestBean bean = mMyAsyncListUtil.getItem(position);
        // 有可能獲取爲空,這是可以顯示加載中,等待同步數據。
        if (bean == null){
            holder.mTvName.setText("加載中...");
        }else {
            holder.mTvName.setText(bean.getName());
        }
    }
    ......
}

註釋還是很清楚的,直接上效果圖:

在這裏插入圖片描述

3.AsyncListDiffer

雖然SortedListAsyncListUtil很方便了,但是大多數的列表都無需我們排序和加載本地數據,大多是獲取網絡數據展示。這個時候就可以使用DiffUtil了。DiffUtil是support-v7:24.2.0中的新工具類,它用來比較新舊兩個數據集,尋找最小變化量,定向刷新列表。關於DiffUtil的介紹很早之前在張旭童的【Android】RecyclerView的好伴侶:詳解DiffUtil 博客中就有詳細介紹,我這裏就不贅述了。

不過DiffUtil的問題在於計算數據差異DiffUtil.calculateDiff(mDiffCallback)時是一個耗時操作,需要我們放到子線程去處理,最後在主線程刷新。爲了方便這一操作,在support-v7:27.1.0又新增了一個DiffUtil的封裝類,那就是AsyncListDiffer

首先先上效果圖,一個簡單的列表展示,同時增、刪、改操作。

在這裏插入圖片描述

我用AsyncListDiffer來實現這一效果。首先實現DiffUtil.ItemCallback,類似SortedList,制定規則,如何區分數據。這裏和DiffUtil用法幾乎一樣,只是它是實現DiffUtil.Callback

public class MyDiffUtilItemCallback extends DiffUtil.ItemCallback<TestBean> {

 	/**
     * 是否是同一個對象
     */  
    @Override
    public boolean areItemsTheSame(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
        return oldItem.getId() == newItem.getId();
    }
 	/**
     * 是否是相同內容
     */ 
    @Override
    public boolean areContentsTheSame(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
        return oldItem.getName().equals(newItem.getName());
    }

	/**
     * areItemsTheSame()返回true而areContentsTheSame()返回false時調用,也就是說兩個對象代表的數據是一條,但是內容更新了。此方法爲定向刷新使用,可選。
     */
    @Nullable
    @Override
    public Object getChangePayload(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
        Bundle payload = new Bundle();

        if (!oldItem.getName().equals(newItem.getName())) {
            payload.putString("KEY_NAME", newItem.getName());
        }

        if (payload.size() == 0){
            //如果沒有變化 就傳空
            return null;
        }
        return payload;
    }
}

Adapter部分有兩種實現方法,一種是實現RecyclerView.Adapter

public class AsyncListDifferAdapter extends RecyclerView.Adapter<AsyncListDifferAdapter.ViewHolder> {
   
    private LayoutInflater mInflater;
    // 數據的操作由AsyncListDiffer實現
    private AsyncListDiffer<TestBean> mDiffer;
    
    public AsyncListDifferAdapter(Context mContext) {
    	// 初始化AsyncListDiffe
        mDiffer = new AsyncListDiffer<>(this, new MyDiffUtilItemCallback());
        mInflater = LayoutInflater.from(mContext);
    }

    public void setData(TestBean mData){
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mDiffer.getCurrentList());
        mList.add(mData);
        mDiffer.submitList(mList);
    }

    public void setData(List<TestBean> mData){
    	// 由於DiffUtil是對比新舊數據,所以需要創建新的集合來存放新數據。
    	// 實際情況下,每次都是重新獲取的新數據,所以無需這步。
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mData);
        mDiffer.submitList(mList);
    }

    public void removeData(int index){
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mDiffer.getCurrentList());
        mList.remove(index);
        mDiffer.submitList(mList);
    }
    
    public void clear(){
        mDiffer.submitList(null);
    }
    
    @Override
    @NonNull
    public AsyncListDifferAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            Bundle bundle = (Bundle) payloads.get(0);
            holder.mTvName.setText(bundle.getString("KEY_NAME"));
        }
    }

    @Override
    public void onBindViewHolder(@NonNull AsyncListDifferAdapter.ViewHolder holder, final int position) {
        TestBean bean = mDiffer.getCurrentList().get(position);
        holder.mTvName.setText(bean.getName());
    }
    
    @Override
    public int getItemCount() {
        return mDiffer.getCurrentList().size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

       ......
    }
}

另一種Adapter寫法可以實現ListAdapter,它的內部幫我們實現了getItemCount()getItem()AsyncListDiffer的初始化。

源碼如下,很簡單:

public abstract class ListAdapter<T, VH extends ViewHolder> extends Adapter<VH> {
    private final AsyncListDiffer<T> mHelper;

    protected ListAdapter(@NonNull ItemCallback<T> diffCallback) {
        this.mHelper = new AsyncListDiffer(new AdapterListUpdateCallback(this), (new Builder(diffCallback)).build());
    }

    protected ListAdapter(@NonNull AsyncDifferConfig<T> config) {
        this.mHelper = new AsyncListDiffer(new AdapterListUpdateCallback(this), config);
    }

    public void submitList(@Nullable List<T> list) {
        this.mHelper.submitList(list);
    }

    protected T getItem(int position) {
        return this.mHelper.getCurrentList().get(position);
    }

    public int getItemCount() {
        return this.mHelper.getCurrentList().size();
    }
}

不過有個缺點,沒有提供直接獲取當前集合的getCurrentList()方法。所以需要自己維護一個集合。希望以後可以添加上吧。所以現階段我還是不推薦這種寫法。。。不過我們可以去做這個封裝。

public class MyListAdapter extends ListAdapter<TestBean, MyListAdapter.ViewHolder> {
   
    private LayoutInflater mInflater;
    // 自己維護的集合
    private List<TestBean> mData = new ArrayList<>();
    
    public MyListAdapter(Context mContext) {
        super(new MyDiffUtilItemCallback());
        mInflater = LayoutInflater.from(mContext);
    }

    public void setData(TestBean testBean){
        mData.add(testBean);
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mData);
        // 提交新的數據集
        submitList(mList);
    }

    public void setData(List<TestBean> list){
        mData.clear();
        mData.addAll(list);
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mData);
        submitList(mList);
    }

    public void removeData(int index){
        mData.remove(index);
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mData);
        submitList(mList);
    }

    public void clear(){
        mData.clear();
        submitList(null);
    }
    
    @Override
    @NonNull
    public MyListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            Bundle bundle = (Bundle) payloads.get(0);
            holder.mTvName.setText(bundle.getString("KEY_NAME"));
        }
    }

    @Override
    public void onBindViewHolder(@NonNull MyListAdapter.ViewHolder holder, final int position) {
        TestBean bean = getItem(position);
        holder.mTvName.setText(bean.getName());
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
		......
    }
}

最後就是Activity了:

public class AsyncListDifferActivity extends AppCompatActivity {

    private AsyncListDifferAdapter mAsyncListDifferAdapter;
    private int count = 10;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sorted_list);
        RecyclerView mRecyclerView = findViewById(R.id.rv);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mAsyncListDifferAdapter = new AsyncListDifferAdapter(this);
        mRecyclerView.setAdapter(mAsyncListDifferAdapter);
        initData();
    }

    private void addData() {
        mAsyncListDifferAdapter.setData(new TestBean(count, "Item " + count));
        count ++;
    }

    private List<TestBean> mList = new ArrayList();

    private void initData() {
        mList.clear();
        for (int i = 0; i < 10; i++){
            mList.add(new TestBean(i, "Item " + i));
        }
        mAsyncListDifferAdapter.setData(mList);
    }

    private void updateData() {
        mList.clear();
        for (int i = 9; i >= 0; i--){
            mList.add(new TestBean(i, "Item " + i));
        }
        mAsyncListDifferAdapter.setData(mList);
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
        return true;
    }
    
    private Random mRandom = new Random();

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int i = item.getItemId();
        if (i == R.id.menu_add) {
            addData();
        } else if (i == R.id.menu_update) {
            updateData();
        } else if (i == R.id.menu_delete) {
            if (mAsyncListDifferAdapter.getItemCount() > 0){
                mAsyncListDifferAdapter.removeData(mRandom.nextInt(mAsyncListDifferAdapter.getItemCount()));
            }
        }else if (i == R.id.menu_clear){
            mAsyncListDifferAdapter.clear();
        }
        return true;
    }
}

我們簡單的看一下AsyncListDiffersubmitList源碼:

public void submitList(@Nullable final List<T> newList) {
        final int runGeneration = ++this.mMaxScheduledGeneration;
        if (newList != this.mList) {
        	if (newList == null) {
            	// 新數據爲null時清空列表
                int countRemoved = this.mList.size();
                this.mList = null;
                this.mReadOnlyList = Collections.emptyList();
                this.mUpdateCallback.onRemoved(0, countRemoved);
            } else if (this.mList == null) {
            	// 舊數據爲null時添加數據
                this.mList = newList;
                this.mReadOnlyList = Collections.unmodifiableList(newList);
                this.mUpdateCallback.onInserted(0, newList.size());
            } else {
                final List<T> oldList = this.mList;
                // 計算數據差異放在子線程
                this.mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
                    public void run() {
                        final DiffResult result = DiffUtil.calculateDiff(new Callback() {
                           ...
                        });
                        // 主線程刷新列表
                        AsyncListDiffer.this.mMainThreadExecutor.execute(new Runnable() {
                            public void run() {
                                if (AsyncListDiffer.this.mMaxScheduledGeneration == runGeneration) {
                                    AsyncListDiffer.this.latchList(newList, result);
                                }

                            }
                        });
                    }
                });
            }
        }
    }


void latchList(@NonNull List<T> newList, @NonNull DiffResult diffResult) {
     this.mList = newList;
     this.mReadOnlyList = Collections.unmodifiableList(newList);
     // 熟悉的dispatchUpdatesTo方法
     diffResult.dispatchUpdatesTo(this.mUpdateCallback);
}

AsyncListDiffer就是在這裏幫我們做了線程的處理。方便我們正確規範的使用。

4.SnapHelper

SnapHelper是support-v7:24.2.0新增的,用於控制RecyclerView滑動停止後Item的對齊方式。默認提供了兩種對齊方式PagerSnapHelperLinearSnapHelperPagerSnapHelper 和ViewPage效果一樣,一次滑動一頁。LinearSnapHelper這是Item居中對齊。使用方式非常簡單:

 PagerSnapHelper mPagerSnapHelper = new PagerSnapHelper();
 mPagerSnapHelper.attachToRecyclerView(mRecyclerView);

效果如下:

在這裏插入圖片描述

當然我們可以自定義SnapHelper,來實現我們想要的對齊方式,下面我們來實現一下左對齊

public class MySnapHelper extends LinearSnapHelper{

    /**
     * 水平、垂直方向的度量
     */
    @Nullable
    private OrientationHelper mVerticalHelper;
    @Nullable
    private OrientationHelper mHorizontalHelper;

    @NonNull
    private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) {
        if (mVerticalHelper == null) {
            mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
        }
        return mVerticalHelper;
    }

    @NonNull
    private OrientationHelper getHorizontalHelper(@NonNull RecyclerView.LayoutManager layoutManager) {
        if (mHorizontalHelper == null) {
            mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
        }
        return mHorizontalHelper;
    }
    
    /**
     * 計算出View對齊到指定位置,所需的x、y軸上的偏移量。
     */
    @Nullable
    @Override
    public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
        int[] out = new int[2];
        
        // 水平方向滑動時計算x方向,否則偏移爲0
        if (layoutManager.canScrollHorizontally()) {
            out[0] = distanceToStart(layoutManager, targetView, getHorizontalHelper(layoutManager));
        } else {
            out[0] = 0;
        }

        // 垂直方向滑動時計算y方向,否則偏移爲0
        if (layoutManager.canScrollVertically()) {
            out[1] = distanceToStart(layoutManager, targetView, getVerticalHelper(layoutManager));
        } else {
            out[1] = 0;
        }
        return out;
    }

    private int distanceToStart(RecyclerView.LayoutManager layoutManager, View targetView, OrientationHelper helper) {
        // RecyclerView的邊界x值,也就是左側Padding值
        final int start;
        if (layoutManager.getClipToPadding()) {
            start = helper.getStartAfterPadding();
        } else {
            start = 0;
        }
        return helper.getDecoratedStart(targetView) - start;
    }

    /**
     * 查找需要對齊的View
     */
    @Nullable
    @Override
    public View findSnapView(RecyclerView.LayoutManager layoutManager) {
        if (layoutManager.canScrollVertically()) {
            return findStartView(layoutManager, getVerticalHelper(layoutManager));
        } else {
            return findStartView(layoutManager, getHorizontalHelper(layoutManager));
        }
    }

    private View findStartView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {
        int childCount = layoutManager.getChildCount();
        if (childCount == 0) {
            return null;
        }

        View closestChild = null;
        final int start;
        if (layoutManager.getClipToPadding()) {
            start = helper.getStartAfterPadding();
        } else {
            start = 0;
        }
        int absClosest = Integer.MAX_VALUE;
        
        for (int i = 0; i < childCount; i++) {
            final View child = layoutManager.getChildAt(i);
            // ItemView 的左側座標
            int childStart = helper.getDecoratedStart(child);
            // 計算此ItemView 與 RecyclerView左側的距離
            int absDistance = Math.abs(childStart - start);

            // 找到那個最靠近左側的ItemView然後返回
            if (absDistance < absClosest) {
                absClosest = absDistance;
                closestChild = child;
            }
        }
        
        return closestChild;
    }

    /**
     * 找到需要對齊的View的position,主要用於fling 操作
     */
    @Override
    public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
        // 左對齊和居中對齊一樣,無需自定義處理
        return super.findTargetSnapPosition(layoutManager, velocityX, velocityY);
    }
}

上面的註釋已將關鍵地方註明,效果我就不展示了。大家可以下載代碼去體驗。本篇所有代碼已上傳至Github。希望點贊支持!!

5.參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章