Android技能——上拉加载更多

2018-07-29  本文已影响0人  上杉奏

编写程序时经常会使用到下拉刷新和上拉加载更多。下拉刷新很简单,使用Google的SwipRefreshLayout就能非常方便地实现。而上拉加载更多则稍微复杂一些。

效果

上拉加载更多.GIF

思路:

1.实现上拉加载更多需要使用到RecyclerView.OnScrollerListener这个类。

当执行mRecyclerView.addOnScrollListener(mOnScrollListener)注册监听后,在OnScrollListener的onScrolled()回调中就可以监听到当前mRecyclerView的滑动事件。在监听到滑动事件时判断当前是否需要执行加载更多,如果需要加载更多,就通过抽象方法onLoadMore()将加载更多的事件从mOnScrollListener中传递出去

2.编写Adapter。
重写onAttachedToRecyclerView()方法,初始化OnScrollListener并调用mRecyclerView.addOnScrollListener(mOnScrollListener)来注册滑动事件监听,同时实现onLoadMore()方法,通过Adapter中的接口进一步将onLoadMore()事件由Adapter传递到Activity中

3.此时已经能够在Activity中来具体实现onLoadMore()了,这里执行具体的加载更多操作。

4.数据加载完毕时更新数据。

虽然整个思路看起来简单明了,但实际实现过程中还是有些麻烦的,比如以下两个问题。

1.什么时候下拉加载更多

2.如何去正确的更新UI

实现:

1.自定义一个继承自OnScrollListener的RecyclerOnScrollerListener。
    public abstract class RecyclerOnScrollerListener extends RecyclerView.OnScrollListener {
    private int currentPage = 1;
    //是否正在加载
    private boolean isLoading = false;
    //是否能够加载更多
    private boolean isCanLoadMore = true;
    private RecyclerView mRecyclerView;

    public RecyclerOnScrollerListener(RecyclerView recyclerView) {
        super();
        this.mRecyclerView = recyclerView;
    }

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        //能够加载更多
        if (isCanLoadMore) {
            //已滑动到最底部
            if (isSlideToBottom()) {
                currentPage++;
                onLoadMore(currentPage);
                isLoading = true;
            }
        }
    }

    /*
    * 是否滑动到最底部
    *
    * !isLoading, 不在加载过程中
    * lastVisibleItemPosition == totalItemCount - 1, 当前可见的最后一个item是列表的最后一个
    * */
    private boolean isSlideToBottom() {
        if (mRecyclerView == null) {
            return false;
        }

        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
        if (layoutManager instanceof LinearLayoutManager) {
            int visibleItemCount = layoutManager.getChildCount();
            int totalItemCount = layoutManager.getItemCount();
            int lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
            return !isLoading && visibleItemCount > 0 && lastVisibleItemPosition == totalItemCount - 1;
        }
        return false;
    }

    //抽象方法,用来传递加载更多事件
    public abstract void onLoadMore(int currentPage);

    public void setCanLoadMore(boolean load) {
        isCanLoadMore = load;
    }

    public void setLoading(boolean load) {
        isLoading = load;
    }

}

本类中加入了两个变量,isLoading和isCanLoadMore,用来控制加载更多事件。
这里通过isSlideToBottom()方法来判断是否滑动到最底部,结合isCanLoadMore和isSlideToBottom共同判断是否加载更多,即当能够加载更多且滑动到最底部时执行加载更多。这就解决了问题1。

2.编写Adapter,这里使用和郭神同样的Fruit为例。
public class FruitAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<Fruit> mFruitList;
    private Context mContext;
    private RecyclerOnScrollerListener mOnScrollListener;
//    private RecyclerView.AdapterDataObserver mAdapterDataObserver;

    private static final int VIEW_TYPE_CONTENT = 0;
    private static final int VIEW_TYPE_FOOTER = 1;
    private boolean isCanLoadMore = true;

    private Animation rotateAnimation;

    private static final int PER_PAGE = 10;

    public FruitAdapter(List<Fruit> fruitList) {
        this.mFruitList = fruitList;
    }


    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (mContext == null) {
            mContext = parent.getContext();
        }
        if (rotateAnimation == null) {
            rotateAnimation = AnimationUtils.loadAnimation(mContext, R.anim.loading);
            rotateAnimation.setInterpolator(new LinearInterpolator());
        }
        if (viewType == VIEW_TYPE_CONTENT) {
            return new ContentViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_fruit, parent, false));
        } else {

            return new FooterViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_footer, parent, false));
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder.getItemViewType() == VIEW_TYPE_CONTENT) {
            Fruit fruit = mFruitList.get(position);
            //这里必须强制转换
            //如果外层的判断条件改为if(holder instance ContentViewHolder),这里输入holder后会自动转换
            ((ContentViewHolder) holder).tvName.setText(fruit.getName());
            ((ContentViewHolder) holder).ivImage.setImageResource(fruit.getImageId());
        } else {
            Log.d("mytest", "isCanLoadMore: " + isCanLoadMore);
            if (isCanLoadMore) {
                ((FooterViewHolder) holder).showLoading();
            } else {
                ((FooterViewHolder) holder).showTextOnly("无更多数据");
            }
        }

    }

    /*
    * 本例中所有数据加载完毕后还是保留footerView并显示已加载完全,所以footerView一直存在。
    * */
    @Override
    public int getItemCount() {
//        return isCanLoadMore ? mFruitList.size() + 1 : mFruitList.size();
        return mFruitList.size() + 1;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == getItemCount() - 1) {
            return VIEW_TYPE_FOOTER;
        }
        return VIEW_TYPE_CONTENT;
    }

    //ContentView,水果们
    class ContentViewHolder extends RecyclerView.ViewHolder {
        TextView tvName = itemView.findViewById(R.id.tv_name);
        ImageView ivImage = itemView.findViewById(R.id.iv_image);

        public ContentViewHolder(View itemView) {
            super(itemView);
        }
    }

    //底部的FooterView
    class FooterViewHolder extends RecyclerView.ViewHolder {

        ImageView ivLoading = itemView.findViewById(R.id.iv_loading);
        TextView tvLoading = itemView.findViewById(R.id.tv_loading);

        public FooterViewHolder(View itemView) {
            super(itemView);
        }

        void showTextOnly(String s) {
            Log.d("mytest", "showTextOnly: " + s);
            ivLoading.setVisibility(View.INVISIBLE);
            tvLoading.setText(s);
        }

        void showLoading() {
            Log.i("mytest", "show loading");
            ivLoading.setImageResource(R.mipmap.ic_launcher);
            tvLoading.setText("正在加载...");
            if (ivLoading != null) {
                ivLoading.startAnimation(rotateAnimation);
            }
        }

    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        mOnScrollListener = new RecyclerOnScrollerListener(recyclerView) {
            @Override
            public void onLoadMore(int currentPage) {
                Log.i("loadingtest", "currentPage: " + currentPage);
                mOnLoadMoreListener.onLoadMore(currentPage);
            }
        };
        recyclerView.addOnScrollListener(mOnScrollListener);
//        mAdapterDataObserver = new RecyclerView.AdapterDataObserver() {
//            @Override
//            public void onChanged() {
//                super.onChanged();
//            }
//        };
        //初始化的时候如果未填满一页,那么肯定就没有更多数据了
        if (mFruitList.size() < PER_PAGE) {
            setCanLoadMore(false);
        } else {
            setCanLoadMore(true);
        }
    }


    @Override
    public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
        super.onDetachedFromRecyclerView(recyclerView);
        if (mOnScrollListener != null) {
            recyclerView.removeOnScrollListener(mOnScrollListener);
        }
//        if (mAdapterDataObserver != null) {
//            unregisterAdapterDataObserver(mAdapterDataObserver);
//        }
        mOnScrollListener = null;
    }

    public void setData(List<Fruit> list) {
        mFruitList = list;
        notifyDataSetChanged();
    }

    /*
    * 数据加载完毕时执行setCanLoadMore(),此时isLoading都置为false
    * */
    public void setCanLoadMore(boolean isCanLoadMore) {
        this.isCanLoadMore = isCanLoadMore;
        mOnScrollListener.setCanLoadMore(isCanLoadMore);
        mOnScrollListener.setLoading(false);
    }


    public interface OnLoadMoreListener {
        void onLoadMore(int currentPage);
    }

    private OnLoadMoreListener mOnLoadMoreListener;

    public void setOnLoadMoreListener(OnLoadMoreListener listener) {
        this.mOnLoadMoreListener = listener;
    }

}

Adapter的编写主要有以下几点:
1.因为有footerView提示加载更多,所以这里有两个自定义的ViewHolder,ContentViewHolder和FooterViewHolder,分别用来存放recyclerView的水果们,和底部的FooterView。
2.与一般的RecyclerView相比,这里主要是多了onAttachedToRecyclerView()和onDetachedFromRecyclerView()两个重写方法。
在onAttachedToRecyclerView()进行思路的第2步,在onDetachedFromRecyclerView()注销监听。
3.setCanLoadMore()一般在加载完数据时候调用,重置isCanLoadMore的状态。

3.编写MainActivity
public class MainActivity extends AppCompatActivity {

    private RecyclerView mRvFruits;
    private List<Fruit> mFruitList = new ArrayList<>();
    private FruitAdapter mAdapter;
    private static final int PER_PAGE = 10;

    private int mCurrentPage = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init();

    }

    private void init() {
        initFruits();
        mAdapter = new FruitAdapter(mFruitList);
        mRvFruits = findViewById(R.id.rv_fruits);
        mRvFruits.setLayoutManager(new LinearLayoutManager(this));
        mRvFruits.setAdapter(mAdapter);
        mAdapter.setOnLoadMoreListener(new FruitAdapter.OnLoadMoreListener() {
            @Override
            public void onLoadMore(int currentPage) {
                mCurrentPage = currentPage;
                loadMoreTest();
            }
        });
    }

    private void initFruits() {
        Log.i("mytest", "initFruits");
        Fruit mango = new Fruit(getRandomLenthName("Mango"), R.drawable.mango);
        mFruitList.add(mango);
        Fruit orange = new Fruit(getRandomLenthName("Orange"), R.drawable.orange);
        mFruitList.add(orange);
        Fruit pear = new Fruit(getRandomLenthName("Pear"), R.drawable.pear);
        mFruitList.add(pear);
        Fruit pineapple = new Fruit(getRandomLenthName("Pineapple"), R.drawable.pineapple);
        mFruitList.add(pineapple);
        Fruit strawberry = new Fruit(getRandomLenthName("Strawberry"), R.drawable.strawberry);
        mFruitList.add(strawberry);
        Fruit watermelon = new Fruit(getRandomLenthName("Watermelon"), R.drawable.watermelon);
        mFruitList.add(watermelon);
        Fruit apple = new Fruit(getRandomLenthName("Apple"), R.drawable.apple);
        mFruitList.add(apple);
        Fruit banana = new Fruit(getRandomLenthName("Banana"), R.drawable.banana);
        mFruitList.add(banana);
        Fruit cherry = new Fruit(getRandomLenthName("Cherry"), R.drawable.cherry);
        mFruitList.add(cherry);
        Fruit grape = new Fruit(getRandomLenthName("Grape"), R.drawable.grape);
        mFruitList.add(grape);
    }

    //模拟加载网络数据
    private void loadMoreTest() {
        Observable.timer(2000, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(Long aLong) throws Exception {
                        //第一个条件是有没有获取到新数据,第二个条件是新数据是否填满了一整页
                        //这里假如3页之后没有更多数据了,这里的条件根据实际网络请求的返回情况来确定。
                        Log.d("loadingtest", "main current page: " + mCurrentPage);
                        if (mCurrentPage < 4) {
                            List<Fruit> loadMoreList = new ArrayList<>();
                            Fruit pear = new Fruit(getRandomLenthName("Pear"), R.drawable.pear);
                            loadMoreList.add(pear);
                            Fruit pineapple = new Fruit(getRandomLenthName("Pineapple"), R.drawable.pineapple);
                            loadMoreList.add(pineapple);
                            Fruit strawberry = new Fruit(getRandomLenthName("Strawberry"), R.drawable.strawberry);
                            loadMoreList.add(strawberry);
                            Fruit pineapple2 = new Fruit(getRandomLenthName("Pineapple"), R.drawable.pineapple);
                            loadMoreList.add(pineapple2);
                            Fruit strawberry2 = new Fruit(getRandomLenthName("Strawberry"), R.drawable.strawberry);
                            loadMoreList.add(strawberry2);
                            Fruit watermelon = new Fruit(getRandomLenthName("Watermelon"), R.drawable.watermelon);
                            loadMoreList.add(watermelon);
                            Fruit apple = new Fruit(getRandomLenthName("Apple"), R.drawable.apple);
                            loadMoreList.add(apple);
                            Fruit banana = new Fruit(getRandomLenthName("Banana"), R.drawable.banana);
                            loadMoreList.add(banana);
                            Fruit cherry = new Fruit(getRandomLenthName("Cherry"), R.drawable.cherry);
                            loadMoreList.add(cherry);
                            Fruit grape = new Fruit(getRandomLenthName("Grape"), R.drawable.grape);
                            loadMoreList.add(grape);
                            mFruitList.addAll(loadMoreList);

                            //如果未填满一整页,那么肯定没有更多数据了
                            Log.d("loadingtest", "size1: " + mFruitList.size() + "  -  size2: " + mCurrentPage * PER_PAGE);
                            if (mFruitList.size() == mCurrentPage * PER_PAGE) {
                                mAdapter.setCanLoadMore(true);
                            } else {
                                mAdapter.setCanLoadMore(false);
                            }
                            mAdapter.setData(mFruitList);

                        } else {
                            //网络数据已加载完
                            mAdapter.setCanLoadMore(false);
                            mAdapter.notifyDataSetChanged();
                        }
                    }
                });

    }

    private String getRandomLenthName(String name) {
        Random random = new Random();
        int length = random.nextInt(20) + 1;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(name);
        }
        return builder.toString();
    }

}

在init()中初始化mRvFruits, 并设定加载更多的操作是loadMoreTest()。在loadMoreTest()中先判断了有没有获取到新数据,然后再判断新数据是否填满了一页。
1.如果没有新数据就说明数据已加载完毕。setCanLoadMore(false),并更新适配器。
2.如果有新数据,但是新数据没有填满一页,也说明数据已经加载完毕。设置setCanLoadMore(false),更新数据。
3.如果有新数据并且新数据填满了一页,说明可能还有更多的数据,可以加载更多,setCanLoadMore(true),更新数据。


以上就是Android上拉加载更多的思路和实现方法,希望能够帮助到你。Demo上传到了我的github,需要的朋友们可以去这里下载:
Kanade —— 上拉加载更多Demo

上一篇下一篇

猜你喜欢

热点阅读