美文网首页
【Android面试题】高级UI面试题——如何给ListView

【Android面试题】高级UI面试题——如何给ListView

作者: 小城哇哇 | 来源:发表于2023-09-10 17:31 被阅读0次

    如何给ListView & RecyclerView加上拉刷新 & 下拉加载更多机制

    这道题想考察什么?

    考察同学是否对列表下拉刷新与上拉加载的实现熟悉。

    考生应该如何回答

    ListView

    下面我们通过自定义ListView,实现下拉刷新与上拉加载,实现的关键点:

    • 为ListView添加头布局和底布局。
    • 通过改变头布局的paddingTop值,来控制控件的显示和隐藏
    • 根据我们滑动的状态,动态修改头部布局和底部布局。

    代码如下:

    public class CustomRefreshListView extends ListView implements OnScrollListener{
       /**
         * 头布局
         */
        private View headerView;
    
        /**
         * 头部布局的高度
         */
        private int headerViewHeight;
    
        /**
         * 头部旋转的图片
         */
        private ImageView iv_arrow;
    
        /**
         * 头部下拉刷新时状态的描述
         */
        private TextView tv_state;
    
        /**
         * 下拉刷新时间的显示控件
         */
        private TextView tv_time;
    
        /**
         * 底部布局
         */
        private View footerView;
    
        /**
         * 底部旋转progressbar
         */
        private ProgressBar pb_rotate;
    
        /**
         * 底部布局的高度
         */
        private int footerViewHeight;
    
        /**
         * 按下时的Y坐标
         */
        private int downY;
    
        private final int PULL_REFRESH = 0;//下拉刷新的状态
        private final int RELEASE_REFRESH = 1;//松开刷新的状态
        private final int REFRESHING = 2;//正在刷新的状态
    
        /**
         * 当前下拉刷新处于的状态
         */
        private int currentState = PULL_REFRESH;
    
        /**
         * 头部布局在下拉刷新改变时,图标的动画
         */
        private RotateAnimation upAnimation,downAnimation;
    
        /**
         * 当前是否在加载数据
         */
        private boolean isLoadingMore = false;
    
        public CustomRefreshListView(Context context) {
            this(context,null);
        }
    
        public CustomRefreshListView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        private void init(){
            //设置滑动监听
            setOnScrollListener(this);
            //初始化头布局
            initHeaderView();
            //初始化头布局中图标的旋转动画
            initRotateAnimation();
            //初始化为尾布局
            initFooterView();
        }
    
        /**
         * 初始化headerView
         */
        private void initHeaderView() {
            headerView = View.inflate(getContext(), R.layout.head_custom_listview, null);
            iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);
            pb_rotate = (ProgressBar) headerView.findViewById(R.id.pb_rotate);
            tv_state = (TextView) headerView.findViewById(R.id.tv_state);
            tv_time = (TextView) headerView.findViewById(R.id.tv_time);
    
            //测量headView的高度
            headerView.measure(0, 0);
            //获取高度,并保存
            headerViewHeight = headerView.getMeasuredHeight();
            //设置paddingTop = -headerViewHeight;这样,该控件被隐藏
            headerView.setPadding(0, -headerViewHeight, 0, 0);
            //添加头布局
            addHeaderView(headerView);
        }
    
        /**
         * 初始化旋转动画
         */
        private void initRotateAnimation() {
    
            upAnimation = new RotateAnimation(0, -180, 
                    RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                    RotateAnimation.RELATIVE_TO_SELF, 0.5f);
            upAnimation.setDuration(300);
            upAnimation.setFillAfter(true);
    
            downAnimation = new RotateAnimation(-180, -360, 
                    RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                    RotateAnimation.RELATIVE_TO_SELF, 0.5f);
            downAnimation.setDuration(300);
            downAnimation.setFillAfter(true);
        }
    
        //初始化底布局,与头布局同理
        private void initFooterView() {
            footerView = View.inflate(getContext(), R.layout.foot_custom_listview, null);
            footerView.measure(0, 0);
            footerViewHeight = footerView.getMeasuredHeight();
            footerView.setPadding(0, -footerViewHeight, 0, 0);
            addFooterView(footerView);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //获取按下时y坐标
                downY = (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
    
                if(currentState==REFRESHING){
                    //如果当前处在滑动状态,则不做处理
                    break;
                }
                //手指滑动偏移量
                int deltaY = (int) (ev.getY() - downY);
    
                //获取新的padding值
                int paddingTop = -headerViewHeight + deltaY;
                if(paddingTop>-headerViewHeight && getFirstVisiblePosition()==0){
                    //向下滑,且处于顶部,设置padding值,该方法实现了顶布局慢慢滑动显现
                    headerView.setPadding(0, paddingTop, 0, 0);
    
                    if(paddingTop>=0 && currentState==PULL_REFRESH){
                        //从下拉刷新进入松开刷新状态
                        currentState = RELEASE_REFRESH;
                        //刷新头布局
                        refreshHeaderView();
                    }else if (paddingTop<0 && currentState==RELEASE_REFRESH) {
                        //进入下拉刷新状态
                        currentState = PULL_REFRESH;
                        refreshHeaderView();
                    }
                    return true;//拦截TouchMove,不让listview处理该次move事件,会造成listview无法滑动
                }
                break;
            case MotionEvent.ACTION_UP:
                if(currentState==PULL_REFRESH){
                    //仍处于下拉刷新状态,未滑动一定距离,不加载数据,隐藏headView
                    headerView.setPadding(0, -headerViewHeight, 0, 0);
                }else if (currentState==RELEASE_REFRESH) {
                    //滑倒一定距离,显示无padding值得headcView
                    headerView.setPadding(0, 0, 0, 0);
                    //设置状态为刷新
                    currentState = REFRESHING;
    
                    //刷新头部布局
                    refreshHeaderView();
    
                    if(listener!=null){
                        //接口回调加载数据
                        listener.onPullRefresh();
                    }
                }
                break;
            }
            return super.onTouchEvent(ev);
        }
    
        /**
         * 根据currentState来更新headerView
         */
        private void refreshHeaderView(){
            switch (currentState) {
            case PULL_REFRESH:
                tv_state.setText("下拉刷新");
                iv_arrow.startAnimation(downAnimation);
                break;
            case RELEASE_REFRESH:
                tv_state.setText("松开刷新");
                iv_arrow.startAnimation(upAnimation);
                break;
            case REFRESHING:
                iv_arrow.clearAnimation();//因为向上的旋转动画有可能没有执行完
                iv_arrow.setVisibility(View.INVISIBLE);
                pb_rotate.setVisibility(View.VISIBLE);
                tv_state.setText("正在刷新...");
                break;
            }
        }
    
        /**
         * 完成刷新操作,重置状态,在你获取完数据并更新完adater之后,去在UI线程中调用该方法
         */
        public void completeRefresh(){
            if(isLoadingMore){
                //重置footerView状态
                footerView.setPadding(0, -footerViewHeight, 0, 0);
                isLoadingMore = false;
            }else {
                //重置headerView状态
                headerView.setPadding(0, -headerViewHeight, 0, 0);
                currentState = PULL_REFRESH;
                pb_rotate.setVisibility(View.INVISIBLE);
                iv_arrow.setVisibility(View.VISIBLE);
                tv_state.setText("下拉刷新");
                tv_time.setText("最后刷新:"+getCurrentTime());
            }
        }
    
        /**
         * 获取当前系统时间,并格式化
         * @return
         */
        private String getCurrentTime(){
            SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
            return format.format(new Date());
        }
    
        private OnRefreshListener listener;
        public void setOnRefreshListener(OnRefreshListener listener){
            this.listener = listener;
        }
        public interface OnRefreshListener{
            void onPullRefresh();
            void onLoadingMore();
        }
    
        /**
         * SCROLL_STATE_IDLE:闲置状态,就是手指松开
         * SCROLL_STATE_TOUCH_SCROLL:手指触摸滑动,就是按着来滑动
         * SCROLL_STATE_FLING:快速滑动后松开
         */
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if(scrollState==OnScrollListener.SCROLL_STATE_IDLE 
                    && getLastVisiblePosition()==(getCount()-1) &&!isLoadingMore){
                isLoadingMore = true;
    
                footerView.setPadding(0, 0, 0, 0);//显示出footerView
                setSelection(getCount());//让listview最后一条显示出来,在页面完全显示出底布局
    
                if(listener!=null){
                    listener.onLoadingMore();
                }
            }
        }
    
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
        }
    }
    

    1.下拉刷新的实现逻辑如下:
    下拉刷新是通过设置setOnTouchListener()方法,监听触摸事件,通过手指滑动的不同处理实现相应逻辑。
    实现比较复杂,分为了三个情况,初始状态(显示下拉刷新),释放刷新状态,刷新状态。
    其中下拉刷新状态和释放状态的改变,是由于手指滑动的不同距离,是在MotionEvent.ACTION_MOVE中进行判断,该判断不处理任何数据逻辑,只是根据手指滑动的偏移量来确定该表UI的显示。
    刷新状态的判断是在MotionEvent.ACTION_UP手指抬起时判断的。这很容易理解,因为最终下拉刷新是否加载数据的确定,是由我们手指离开屏幕时与初始值的偏移量确定的。如果我们的偏移量小于了头布局的高度,代表不刷新,继续隐藏头布局。如果偏移量大于了头布局的高度,则意味着刷新,修改UI,同时通过接口回调,让其持有者进行加载数据。

    2.上拉加载的实现逻辑如下:
    上拉加载和下拉刷新不同,它的实现十分简单,我们通过ListView的滚动监听进行处理相应逻辑。即setOnScrollListener(this)。
    该方法需要实现两个回调方法:

    • public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount):滚动监听的调用。
    • public void onScrollStateChanged(AbsListView view, int scrollState):滑动状态改变的回调。其中scrollState为回调的状态,可能值为
      • SCROLL_STATE_IDLE:闲置状态,手指松开后的状态回调
      • SCROLL_STATE_TOUCH_SCROLL:手指触摸滑动的状态回调
      • SCROLL_STATE_FLING:手指松开后惯性滑动的回调

    我们在onScrollStateChanged中进行判断,主要判断一下条件;

    • 是否是停止状态
    • 是否滑倒最后
    • 是否正在加载数据

    如果符合条件,则开始加载数据,通过接口回调。

    RecyclerView

    使用官方的刷新控件SwipeRefreshLayout来实现下拉刷新,当RecyclerView滑到底部实现下拉加载(进度条效果用RecyclerView加载一个布局实现)
    需要完成控件的下拉监听和上拉监听,其中,下拉监听通过SwipRefreshLayout的setOnRefreshListener()方法监听,而上拉刷新,需要通过监听列表的滚动,当列表滚动到底部时触发事件,具体代码如下:

    public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {
        private SwipeRefreshLayout refreshLayout;
        private RecyclerView recyclerView;
        private LinearLayoutManager layoutManager;
    
        private RecyclerAdapter mAdapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initViews();
        }
    
        private void initViews() {
            refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh_layout);
            recyclerView = (RecyclerView) findViewById(R.id.recycler_list);
            layoutManager = new LinearLayoutManager(this);
    
            // 设置刷新时进度条颜色,最多四种
            refreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary);
            // 设置监听,需要重写onRefresh()方法,顶部下拉时会调用这个方法
            refreshLayout.setOnRefreshListener(this);
    
            mAdapter = new RecyclerAdapter();//自定义的适配器
            recyclerView.setAdapter(mAdapter);
            recyclerView.setLayoutManager(layoutManager);
            recyclerView.addOnScrollListener(new OnRecyclerScrollListener());
        }
    
        /**
          * 用于下拉刷新
          */
        @Override
        public void onRefresh() {
        }
    
        /**
          * 用于上拉加载更多
          */
        public class OnRecyclerScrollListener extends RecyclerView.OnScrollListener {
            int lastVisibleItem = 0;
    
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                
                if (mAdapter != null && newState == RecyclerView.SCROLL_STATE_IDLE 
                    && lastVisibleItem + 1 == mAdapter.getItemCount()) {
                    //滚动到底部了,可以进行数据加载等操作
                }
            }
    
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                lastVisibleItem = layoutManager.findLastVisibleItemPosition();
            }
        }
    }
    

    下面是实现上拉时进度条转动的效果

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">
    
        <TextView
                  android:id="@+id/tv_item_footer_load_more"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:layout_margin="16dp"
                  android:gravity="center"
                  android:text="上拉加载更多"
                  />
    
        <ProgressBar
                     android:id="@+id/pb_item_footer_loading"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:layout_margin="16dp"
                     android:visibility="gone"/>
    </RelativeLayout>
    
    public class RecyclerAdapter extends RecyclerView.Adapter<ViewHolder> {
        private static final int TYPE_CONTENT = 0;
        private static final int TYPE_FOOTER = 1;
    
        private ArrayList<DataBean> dataList;
    
        private ProgressBar pbLoading;
        private TextView tvLoadMore;
    
        public RecyclerAdapter() {
            dataList = new ArrayList<>();
        }
    
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType == TYPE_CONTENT) {
                return new ContentViewHolder(LayoutInflater.from(parent.getContext())
                                             .inflate(R.layout.item_list_content, parent, false));
            } else if (viewType == TYPE_FOOTER) {//加载进度条的布局
                return new FooterViewHolder(LayoutInflater.from(parent.getContext())
                                            .inflate(R.layout.item_list_footer, parent, false));
            }
            return null;
        }
    
        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            int type = getItemViewType(position);
            if (type == TYPE_CONTENT) {
                DataBean bean = dataList.get(position);
                ((ContentViewHolder) holder).tvId.setText("" + bean.getId());
                ((ContentViewHolder) holder).tvName.setText(bean.getName());
            } else if (type == TYPE_FOOTER) {
                pbLoading = ((FooterViewHolder) holder).pbLoading;
                tvLoadMore = ((FooterViewHolder) holder).tvLoadMore;
            }
        }
    
        /**
          * 获取数据集加上一个footer的数量
          */
        @Override
        public int getItemCount() {
            return dataList.size() + 1;
        }
    
    
        @Override
        public int getItemViewType(int position) {
            if (position + 1 == getItemCount()) {
                return TYPE_FOOTER;
            } else {
                return TYPE_CONTENT;
            }
        }
    
        /**
          * 获取数据集的大小
          */
        public int getListSize() {
            return dataList.size();
        }
    
    
    
        /**
          * 内容的ViewHolder
          */
        public static class ContentViewHolder extends ViewHolder {
            private TextView tvId, tvName;
    
            public ContentViewHolder(View itemView) {
                super(itemView);
                tvId = (TextView) itemView.findViewById(R.id.tv_item_id);
                tvName = (TextView) itemView.findViewById(R.id.tv_item_name);
            }
        }
    
        /**
          * footer的ViewHolder
          */
        public static class FooterViewHolder extends ViewHolder {
            private TextView tvLoadMore;
            private ProgressBar pbLoading;
    
            public FooterViewHolder(View itemView) {
                super(itemView);
                tvLoadMore = (TextView) itemView.findViewById(R.id.tv_item_footer_load_more);
                pbLoading = (ProgressBar) itemView.findViewById(R.id.pb_item_footer_loading);
            }
        }
    
        /**
          * 显示正在加载的进度条,滑动到底部时,调用该方法,上拉就显示进度条,隐藏"上拉加载更多"
          */
        public void showLoading() {
            if (pbLoading != null && tvLoadMore != null) {
                pbLoading.setVisibility(View.VISIBLE);
                tvLoadMore.setVisibility(View.GONE);
            }
        }
    
        /**
          * 显示上拉加载的文字,当数据加载完毕,调用该方法,隐藏进度条,显示“上拉加载更多”
          */
        public void showLoadMore() {
            if (pbLoading != null && tvLoadMore != null) {
                pbLoading.setVisibility(View.GONE);
                tvLoadMore.setVisibility(View.VISIBLE);
            }
        }
    }
    

    有需要以上面试题的朋友可以关注一下哇哇,以上都可以分享!!!

    相关文章

      网友评论

          本文标题:【Android面试题】高级UI面试题——如何给ListView

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