Android高级UI面试题汇总(含详细解析二十六)

Android并发编程高级面试题汇总最全最细面试题讲解持续更新中👊👊
👀你想要的面试题这里都有👀
👇👇👇

如何给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"?>

<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"/>
public class RecyclerAdapter extends RecyclerView.Adapter { 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);
    }
}

}

由于面试题内容比较多,篇幅有限,资料已经被整理成了PDF文档,有需要2023年Android中高级最全面试真题答案 完整文档的可扫描下方卡片免费获取~

PS:(文末还有使用ChatGPT机器人小福利哦!!大家不要错过)

目录

img

第一章 Java方面

  • Java基础部分
  • Java集合
  • Java多线程
  • Java虚拟机

img

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值