返回介绍

SwipeRefreshLayout extends ViewGroup

发布于 2024-12-23 22:24:44 字数 5117 浏览 0 评论 0 收藏 0

其实就是一个自定义的 ViewGroup ,结合我们自己平时自定义 ViewGroup 的步骤:

  1. 初始化变量
  2. onMeasure
  3. onLayout
  4. 处理交互 ( dispatchTouchEvent onInterceptTouchEvent onTouchEvent

接下来就按照上面的步骤进行分析。

1. 初始化变量

SwipeRefreshLayout 内部有 2 个 View,一个 圆圈(mCircleView) ,一个内部可滚动的 View(mTarget) 。除了 View,还包含一个 OnRefreshListener 接口,当刷新动画被触发时回调。

/**
 * Constructor that is called when inflating SwipeRefreshLayout from XML.
 *
 * @param context
 * @param attrs
 */
public SwipeRefreshLayout(Context context, AttributeSet attrs) {
  super(context, attrs);

  // 系统默认的最小滚动距离
  mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

  // 系统默认的动画时长
  mMediumAnimationDuration = getResources().getInteger(
      android.R.integer.config_mediumAnimTime);

  setWillNotDraw(false);
  mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);

  // 获取 xml 中定义的属性
  final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
  setEnabled(a.getBoolean(0, true));
  a.recycle();

  // 刷新的圆圈的大小,单位转换成 sp
  final DisplayMetrics metrics = getResources().getDisplayMetrics();
  mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
  mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density);

  // 创建刷新动画的圆圈
  createProgressView();

  ViewCompat.setChildrenDrawingOrderEnabled(this, true);
  // the absolute offset has to take into account that the circle starts at an offset
  mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density;
  // 刷新动画的临界距离值
  mTotalDragDistance = mSpinnerFinalOffset;

  // 通过 NestedScrolling 机制来处理嵌套滚动
  mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
  mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
  setNestedScrollingEnabled(true);
}

创建刷新动画的圆圈

private void createProgressView() {
  mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER/2);
  mProgress = new MaterialProgressDrawable(getContext(), this);
  mProgress.setBackgroundColor(CIRCLE_BG_LIGHT);
  mCircleView.setImageDrawable(mProgress);
  mCircleView.setVisibility(View.GONE);
  addView(mCircleView);
}

初始化的时候创建一个出来一个 View (下拉刷新的圆圈)。可以看出使用背景圆圈是 v4 包里提供的 CircleImageView 控件,中间的是 MaterialProgressDrawable 进度条。 另一个 View 是在 xml 中包含的可滚动视图。

2. onMeasure

onMeasure 确定子视图的大小。

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  if (mTarget == null) {
    // 确定内部要滚动的 View,如 RecycleView
    ensureTarget();
  }
  if (mTarget == null) {
    return;
  }

  // 测量子 View (mTarget)
  mTarget.measure(MeasureSpec.makeMeasureSpec(
      getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
      MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
      getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));

  // 测量刷新的圆圈 mCircleView
  mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY),
      MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY));

  if (!mUsingCustomStart && !mOriginalOffsetCalculated) {
    mOriginalOffsetCalculated = true;
    mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight();
  }

  // 计算 mCircleView 在 ViewGroup 中的索引
  mCircleViewIndex = -1;
  // Get the index of the circleview.
  for (int index = 0; index < getChildCount(); index++) {
    if (getChildAt(index) == mCircleView) {
      mCircleViewIndex = index;
      break;
    }
  }
}

这个步骤确定了 mCircleView 和 SwipeRefreshLayout 的子视图的大小。

3. onLayout

onLayout 主要负责确定各个子视图的位置。

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   // 获取 SwipeRefreshLayout 的宽高
   final int width = getMeasuredWidth();
   final int height = getMeasuredHeight();
   if (getChildCount() == 0) {
     return;
   }
   if (mTarget == null) {
     ensureTarget();
   }
   if (mTarget == null) {
     return;
   }
   // 考虑到给控件设置 padding,去除 padding 的距离
   final View child = mTarget;
   final int childLeft = getPaddingLeft();
   final int childTop = getPaddingTop();
   final int childWidth = width - getPaddingLeft() - getPaddingRight();
   final int childHeight = height - getPaddingTop() - getPaddingBottom();
   // 设置 mTarget 的位置
   child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
   int circleWidth = mCircleView.getMeasuredWidth();
   int circleHeight = mCircleView.getMeasuredHeight();
   // 根据 mCurrentTargetOffsetTop 变量的值来设置 mCircleView 的位置
   mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop,
       (width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight);
}

在 onLayout 中放置了 mCircleView 的位置,注意 顶部位置是 mCurrentTargetOffsetTop ,mCurrentTargetOffsetTop 初始距离是 -mCircleView.getMeasuredHeight() ,所以是在 SwipeRefreshLayout 外。

经过以上几个步骤,SwipeRefreshLayout 创建了子视图,确定他们的大小、位置,现在所有视图可以显示在界面了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文