Bootstrap

Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

<RelativeLayout
android:layout_width=“0dip”
android:layout_height=“60dip”
android:layout_weight=“3”





在这个布局中,我们包含了一个下拉指示箭头,一个下拉状态文字提示,和一个上次更新的时间。当然,还有一个隐藏的旋转进度条,只有正在刷新的时候我们才会将它显示出来。

布局中所有引用的字符串我们都放在 strings.xml 中,如下所示:

<?xml version="1.0" encoding="utf-8"?>

PullToRefreshTest
下拉可以刷新
释放立即刷新
正在刷新…
暂未更新过
上次更新于%1$s前
刚刚更新
时间有问题

然后新建一个 RefreshableView 继承自 LinearLayout,代码如下所示:

public class RefreshableView extends LinearLayout implements OnTouchListener {

/**

  • 下拉状态
    */
    public static final int STATUS_PULL_TO_REFRESH = 0;

/**

  • 释放立即刷新状态
    */
    public static final int STATUS_RELEASE_TO_REFRESH = 1;

/**

  • 正在刷新状态
    */
    public static final int STATUS_REFRESHING = 2;

/**

  • 刷新完成或未刷新状态
    */
    public static final int STATUS_REFRESH_FINISHED = 3;

/**

  • 下拉头部回滚的速度
    */
    public static final int SCROLL_SPEED = -20;

/**

  • 一分钟的毫秒值,用于判断上次的更新时间
    */
    public static final long ONE_MINUTE = 60 * 1000;

/**

  • 一小时的毫秒值,用于判断上次的更新时间
    */
    public static final long ONE_HOUR = 60 * ONE_MINUTE;

/**

  • 一天的毫秒值,用于判断上次的更新时间
    */
    public static final long ONE_DAY = 24 * ONE_HOUR;

/**

  • 一月的毫秒值,用于判断上次的更新时间
    */
    public static final long ONE_MONTH = 30 * ONE_DAY;

/**

  • 一年的毫秒值,用于判断上次的更新时间
    */
    public static final long ONE_YEAR = 12 * ONE_MONTH;

/**

  • 上次更新时间的字符串常量,用于作为SharedPreferences的键值
    */
    private static final String UPDATED_AT = “updated_at”;

/**

  • 下拉刷新的回调接口
    */
    private PullToRefreshListener mListener;

/**

  • 用于存储上次更新时间
    */
    private SharedPreferences preferences;

/**

  • 下拉头的View
    */
    private View header;

/**

  • 需要去下拉刷新的ListView
    */
    private ListView listView;

/**

  • 刷新时显示的进度条
    */
    private ProgressBar progressBar;

/**

  • 指示下拉和释放的箭头
    */
    private ImageView arrow;

/**

  • 指示下拉和释放的文字描述
    */
    private TextView description;

/**

  • 上次更新时间的文字描述
    */
    private TextView updateAt;

/**

  • 下拉头的布局参数
    */
    private MarginLayoutParams headerLayoutParams;

/**

  • 上次更新时间的毫秒值
    */
    private long lastUpdateTime;

/**

  • 为了防止不同界面的下拉刷新在上次更新时间上互相有冲突,使用id来做区分
    */
    private int mId = -1;

/**

  • 下拉头的高度
    */
    private int hideHeaderHeight;

/**

  • 当前处理什么状态,可选值有STATUS_PULL_TO_REFRESH, STATUS_RELEASE_TO_REFRESH,
  • STATUS_REFRESHING 和 STATUS_REFRESH_FINISHED
    */
    private int currentStatus = STATUS_REFRESH_FINISHED;;

/**

  • 记录上一次的状态是什么,避免进行重复操作
    */
    private int lastStatus = currentStatus;

/**

  • 手指按下时的屏幕纵坐标
    */
    private float yDown;

/**

  • 在被判定为滚动之前用户手指可以移动的最大值。
    */
    private int touchSlop;

/**

  • 是否已加载过一次layout,这里onLayout中的初始化只需加载一次
    */
    private boolean loadOnce;

/**

  • 当前是否可以下拉,只有ListView滚动到头的时候才允许下拉
    */
    private boolean ableToPull;

/**

  • 下拉刷新控件的构造函数,会在运行时动态添加一个下拉头的布局。
  • @param context
  • @param attrs
    */
    public RefreshableView(Context context, AttributeSet attrs) {
    super(context, attrs);
    preferences = PreferenceManager.getDefaultSharedPreferences(context);
    header = LayoutInflater.from(context).inflate(R.layout.pull_to_refresh, null, true);
    progressBar = (ProgressBar) header.findViewById(R.id.progress_bar);
    arrow = (ImageView) header.findViewById(R.id.arrow);
    description = (TextView) header.findViewById(R.id.description);
    updateAt = (TextView) header.findViewById(R.id.updated_at);
    touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    refreshUpdatedAtValue();
    setOrientation(VERTICAL);
    addView(header, 0);
    }

/**

  • 进行一些关键性的初始化操作,比如:将下拉头向上偏移进行隐藏,给ListView注册touch事件。
    */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    if (changed && !loadOnce) {
    hideHeaderHeight = -header.getHeight();
    headerLayoutParams = (MarginLayoutParams) header.getLayoutParams();
    headerLayoutParams.topMargin = hideHeaderHeight;
    listView = (ListView) getChildAt(1);
    listView.setOnTouchListener(this);
    loadOnce = true;
    }
    }

/**

  • 当ListView被触摸时调用,其中处理了各种下拉刷新的具体逻辑。
    */
    @Override
    public boolean onTouch(View v, MotionEvent event) {
    setIsAbleToPull(event);
    if (ableToPull) {
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
    yDown = event.getRawY();
    break;
    case MotionEvent.ACTION_MOVE:
    float yMove = event.getRawY();
    int distance = (int) (yMove - yDown);
    // 如果手指是下滑状态,并且下拉头是完全隐藏的,就屏蔽下拉事件
    if (distance <= 0 && headerLayoutParams.topMargin <= hideHeaderHeight) {
    return false;
    }
    if (distance < touchSlop) {
    return false;
    }
    if (currentStatus != STATUS_REFRESHING) {
    if (headerLayoutParams.topMargin > 0) {
    currentStatus = STATUS_RELEASE_TO_REFRESH;
    } else {
    currentStatus = STATUS_PULL_TO_REFRESH;
    }
    // 通过偏移下拉头的topMargin值,来实现下拉效果
    headerLayoutParams.topMargin = (distance / 2) + hideHeaderHeight;
    header.setLayoutParams(headerLayoutParams);
    }
    break;
    case MotionEvent.ACTION_UP:
    default:
    if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
    // 松手时如果是释放立即刷新状态,就去调用正在刷新的任务
    new RefreshingTask().execute();
    } else if (currentStatus == STATUS_PULL_TO_REFRESH) {
    // 松手时如果是下拉状态,就去调用隐藏下拉头的任务
    new HideHeaderTask().execute();
    }
    break;
    }
    // 时刻记得更新下拉头中的信息
    if (currentStatus == STATUS_PULL_TO_REFRESH
    || currentStatus == STATUS_RELEASE_TO_REFRESH) {
    updateHeaderView();
    // 当前正处于下拉或释放状态,要让ListView失去焦点,否则被点击的那一项会一直处于选中状态
    listView.setPressed(false);
    listView.setFocusable(false);
    listView.setFocusableInTouchMode(false);
    lastStatus = currentStatus;
    // 当前正处于下拉或释放状态,通过返回true屏蔽掉ListView的滚动事件
    return true;
    }
    }
    return false;
    }

/**

  • 给下拉刷新控件注册一个监听器。
  • @param listener
  •        监听器的实现。
    
  • @param id
  •        为了防止不同界面的下拉刷新在上次更新时间上互相有冲突, 请不同界面在注册下拉刷新监听器时一定要传入不同的id。
    

*/
public void setOnRefreshListener(PullToRefreshListener listener, int id) {
mListener = listener;
mId = id;
}

/**

  • 当所有的刷新逻辑完成后,记录调用一下,否则你的ListView将一直处于正在刷新状态。
    */
    public void finishRefreshing() {
    currentStatus = STATUS_REFRESH_FINISHED;
    preferences.edit().putLong(UPDATED_AT + mId, System.currentTimeMillis()).commit();
    new HideHeaderTask().execute();
    }

/**

  • 根据当前ListView的滚动状态来设定 {@link #ableToPull}
  • 的值,每次都需要在onTouch中第一个执行,这样可以判断出当前应该是滚动ListView,还是应该进行下拉。
  • @param event
    */
    private void setIsAbleToPull(MotionEvent event) {
    View firstChild = listView.getChildAt(0);
    if (firstChild != null) {
    int firstVisiblePos = listView.getFirstVisiblePosition();
    if (firstVisiblePos == 0 && firstChild.getTop() == 0) {
    if (!ableToPull) {
    yDown = event.getRawY();
    }
    // 如果首个元素的上边缘,距离父布局值为0,就说明ListView滚动到了最顶部,此时应该允许下拉刷新
    ableToPull = true;
    } else {
    if (headerLayoutParams.topMargin != hideHeaderHeight) {
    headerLayoutParams.topMargin = hideHeaderHeight;
    header.setLayoutParams(headerLayoutParams);
    }
    ableToPull = false;
    }
    } else {
    // 如果ListView中没有元素,也应该允许下拉刷新
    ableToPull = true;
    }
    }

/**

  • 更新下拉头中的信息。
    */
    private void updateHeaderView() {
    if (lastStatus != currentStatus) {
    if (currentStatus == STATUS_PULL_TO_REFRESH) {
    description.setText(getResources().getString(R.string.pull_to_refresh));
    arrow.setVisibility(View.VISIBLE);
    progressBar.setVisibility(View.GONE);
    rotateArrow();
    } else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
    description.setText(getResources().getString(R.string.release_to_refresh));
    arrow.setVisibility(View.VISIBLE);
    progressBar.setVisibility(View.GONE);
    rotateArrow();
    } else if (currentStatus == STATUS_REFRESHING) {
    description.setText(getResources().getString(R.string.refreshing));
    progressBar.setVisibility(View.VISIBLE);
    arrow.clearAnimation();
    arrow.setVisibility(View.GONE);
    }
    refreshUpdatedAtValue();
    }
    }

/**

  • 根据当前的状态来旋转箭头。
    */
    private void rotateArrow() {
    float pivotX = arrow.getWidth() / 2f;
    float pivotY = arrow.getHeight() / 2f;

总结

【Android 详细知识点思维脑图(技能树)】

我个人是做Android开发,已经有十来年了,目前在某创业公司任职CTO兼系统架构师。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

最后,赠与大家一句话,共勉!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

由于篇幅有限,这里以图片的形式给大家展示一小部分。

[外链图片转存中…(img-ZQwv490c-1713703350479)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

最后,赠与大家一句话,共勉!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-q2GmR9is-1713703350479)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

;