美文网首页
Android技能——上拉加载更多

Android技能——上拉加载更多

作者: 上杉奏 | 来源:发表于2018-07-29 18:54 被阅读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

    相关文章

      网友评论

          本文标题:Android技能——上拉加载更多

          本文链接:https://www.haomeiwen.com/subject/ghsipftx.html