美文网首页
【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面试题

    Android面试必备: (一)、Java面试题 (二)、Android面试题 (三)、Android高级面试题

  • l 主要分为以下几部分: (1)java 面试题 (2)Android 面试题 (3)高级开发技术面试题 (...

  • Android面试题与解析

    主要分为以下几部分: (1)java面试题 (2)Android面试题 (3)高级开发技术面试题 (4)跨平台Hy...

  • 2019-10-17

    主要分为以下几部分:(1)java面试题 (2)Android面试题 (3)高级开发技术面试题 (4)跨平台Hyb...

  • 面试题

    关注的面试题文集 Android面试题整理 Android工程师面试题大全 Android 面试题总结之Andro...

  • ios面试题

    初级面试题 中级面试题 高级面试题 swift篇

  • 2020Android 高级面试题及答案(Google收录,值得

    本文列举了Android常见高级面试题和答案解析。希望能够帮助到即将准备面试的Android开发朋友! 1.如何对...

  • Android面试总结二

    参考文章 40个Android面试题Java面试题集Android名企面试题及知识点整理Android面试题收集较...

  • Android超实用最全面试大纲(三)

    文章目录: ANR面试题 OOM面试题 Bitmap面试题 UI卡顿面试题 内存泄漏面试题 内存管理面试题 一、A...

  • Android最全面试大纲(三)

    文章目录: ANR面试题 OOM面试题 Bitmap面试题 UI卡顿面试题 内存泄漏面试题 内存管理面试题 一、A...

网友评论

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

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