android 控件 下拉刷新 phoenix 带源码分析
来源:互联网 发布:图灵系列图书 知乎 编辑:程序博客网 时间:2024/05/22 03:22
向纳什致敬,凤凰城永远的英雄!phoenix
github的项目地址:https://github.com/Yalantis/Phoenix
Yalantis 致力于提供世界一流的 Android 和 iOS 应用开发服务,因一些
动画很棒的开源库为大家所熟知
Phoenix
Taurus
Phoenix-Android 旨在提供一个简单的可定制的下拉刷新功能。
<com.hankkin.AnimationPullToRefreshDemo.PullToRefreshView android:id="@+id/pull_to_refresh" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/list_view" android:divider="@null" android:dividerHeight="0dp" android:fadingEdge="none" android:layout_width="match_parent" android:layout_height="match_parent" /> </com.hankkin.AnimationPullToRefreshDemo.PullToRefreshView>
由此我们可以知道,在这个下拉刷新并不是重写了listview,而是在listview的外面套了一层布局,也就是说listview被添加到了Phoenix上,那么我们就能知道Phoenix其实就是一个viewgroup,到这里就差不多知道要重写那几个方法了,自定义viewgroup的话也就onlayout、onmeasure、ontouchevent(如果是自定义view的话一般就重写onmeasure、ondraw、ontouchevent),整理到这里我们就可以来看看那源码了。
源码分析
构造方法
onmeasure
onlayout
头部动画效果实现
构造方法
首先看他的构造方法
public PullToRefreshView(Context context, AttributeSet attrs) {super(context, attrs);TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RefreshView);final int type = a.getInteger(R.styleable.RefreshView_type, STYLE_SUN);a.recycle();mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();mTotalDragDistance = Utils.convertDpToPixel(context, DRAG_MAX_DISTANCE);mRefreshView = new ImageView(context);setRefreshStyle(type);addView(mRefreshView);//保证ondraw会执行,如果是true的话ondraw不会执行setWillNotDraw(false);ViewCompat.setChildrenDrawingOrderEnabled(this, true);}
public void setRefreshStyle(int type) {setRefreshing(false);switch (type) {case STYLE_SUN:mBaseRefreshView = new SunRefreshView(getContext(), this);break;default:throw new InvalidParameterException("Type does not exist");}mRefreshView.setImageDrawable(mBaseRefreshView);}
在这里setRefreshStyle其实就可以直接看成是给头部的imageview设置显示的内容,然后将这个imageview添加到viewgroup中,另外的就是一写参数的初始化。简单的说就是在这个已经包了一个listview的viewgroup中再添加一个imageview。
onMeasure
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);ensureTarget();if (mTarget == null)return;widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingRight() - getPaddingLeft(), MeasureSpec.EXACTLY);heightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);mTarget.measure(widthMeasureSpec, heightMeasureSpec);mRefreshView.measure(widthMeasureSpec, heightMeasureSpec);}private void ensureTarget() {if (mTarget != null)return;if (getChildCount() > 0) {for (int i = 0; i < getChildCount(); i++) {View child = getChildAt(i);if (child != mRefreshView) {mTarget = child;mTargetPaddingBottom = mTarget.getPaddingBottom();mTargetPaddingLeft = mTarget.getPaddingLeft();mTargetPaddingRight = mTarget.getPaddingRight();mTargetPaddingTop = mTarget.getPaddingTop();}}}}
一开始mTarget 是空的,然后到getChildCount方法,想一下这个时候这个viewgroup中也就两个孩子,一个imageview,一个listview,ensureTarget的作用就是把listview的实例赋值给mTarget,以及给几个padding赋值,随后在onmeasure中设置imageview和listview与外层的viewgroup一样大小。
@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {ensureTarget();if (mTarget == null)return;int height = getMeasuredHeight();int width = getMeasuredWidth();int left = getPaddingLeft();int top = getPaddingTop();int right = getPaddingRight();int bottom = getPaddingBottom();mTarget.layout(left, top + mCurrentOffsetTop, left + width - right, top + height - bottom + mCurrentOffsetTop);mRefreshView.layout(left, top, left + width - right, top + height - bottom);}
imageview和listview放在相同的位置。
ontouchevent和onInterceptTouchEvent
如果不知道上面两个方法的关系,可以去看看另一篇文章(http://blog.csdn.net/u012806692/article/details/50820070),首先重写的是拦截的方法@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {if (!isEnabled() || canChildScrollUp() || mRefreshing) {return false;}final int action = MotionEventCompat.getActionMasked(ev);switch (action) {case MotionEvent.ACTION_DOWN:setTargetOffsetTop(0, true);mActivePointerId = MotionEventCompat.getPointerId(ev, 0);mIsBeingDragged = false;final float initialMotionY = getMotionEventY(ev, mActivePointerId);if (initialMotionY == -1) {return false;}mInitialMotionY = initialMotionY;break;case MotionEvent.ACTION_MOVE:if (mActivePointerId == INVALID_POINTER) {return false;}final float y = getMotionEventY(ev, mActivePointerId);if (y == -1) {return false;}final float yDiff = y - mInitialMotionY;if (yDiff > mTouchSlop && !mIsBeingDragged) {mIsBeingDragged = true;}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:mIsBeingDragged = false;mActivePointerId = INVALID_POINTER;break;case MotionEventCompat.ACTION_POINTER_UP:onSecondaryPointerUp(ev);break;}return mIsBeingDragged;}
如果listview没有滑到最顶部或者还在加载刷新中就不执行之后的代码,直接返回false,否则记录按下位置的y坐标,注意这里还有多点触控的知识,这里不理解可以先不用管。到了action_move之后,如果开始滑动(也就是大于mTouchSlop )就拦截touch事件不传递给子view,直接执行自己的ontouchevent方法,这里我们先不管多点触控相关的直接简单理解下,接下来看ontouchevent事件
@Override public boolean onTouchEvent(@NonNull MotionEvent ev) { if (!mIsBeingDragged) { return super.onTouchEvent(ev); } final int action = MotionEventCompat.getActionMasked(ev); switch (action) { case MotionEvent.ACTION_MOVE: { final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); if (pointerIndex < 0) { return false; } final float y = MotionEventCompat.getY(ev, pointerIndex); final float yDiff = y - mInitialMotionY; final float scrollTop = yDiff * DRAG_RATE; mCurrentDragPercent = scrollTop / mTotalDragDistance; if (mCurrentDragPercent < 0) { return false; } float boundedDragPercent = Math.min(1f, Math.abs(mCurrentDragPercent)); float extraOS = Math.abs(scrollTop) - mTotalDragDistance; float slingshotDist = mTotalDragDistance; float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2) / slingshotDist); float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow( (tensionSlingshotPercent / 4), 2)) * 2f; float extraMove = (slingshotDist) * tensionPercent / 2; int targetY = (int) ((slingshotDist * boundedDragPercent) + extraMove); mBaseRefreshView.setPercent(mCurrentDragPercent, true); setTargetOffsetTop(targetY - mCurrentOffsetTop, true); break; } case MotionEventCompat.ACTION_POINTER_DOWN: final int index = MotionEventCompat.getActionIndex(ev); mActivePointerId = MotionEventCompat.getPointerId(ev, index); break; case MotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { if (mActivePointerId == INVALID_POINTER) { return false; } final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); final float y = MotionEventCompat.getY(ev, pointerIndex); final float overScrollTop = (y - mInitialMotionY) * DRAG_RATE; mIsBeingDragged = false; if (overScrollTop > mTotalDragDistance) { setRefreshing(true, true); } else { mRefreshing = false; animateOffsetToStartPosition(); } mActivePointerId = INVALID_POINTER; return false; } } return true;}
代码比较多一步步看,上面我们已经执行到了move,在ontouchevent的move中计算了滑动的百分比,头部有个默认的最大值,看目前的滑动举例是他的百分之几,滑动距离超过最大值时取100%,在case action_move中的代码看着挺多,最重要的就最后的几句,设置头部的显示百分比,还有listview的偏移(相当于magin),随后主要看action_up,在抬起中判断滑动距离是否到了加载的那个指定距离,如果足够了就加载,不够就直接回到初始位置,大致流程就是这样。接下来看看imageview中的内容,就是一个
头部动画效果
其实就是imageview中的内容,一开始其实我们留下了一个问题,回想一下,在onlayout和onmeasure中我们设置的imageview的大小和显示位置和listview的是一样的,那么两个不就叠在一起了吗?接下来看下imageview的内容是什么就明白了。它的构造方法就不看了,就一堆变量的赋值,加载图片等等,直接看自定义view最重要的draw方法@Overridepublic void draw(Canvas canvas) {if (mScreenWidth <= 0) return;final int saveCount = canvas.save();canvas.translate(0, mTop);canvas.clipRect(0, -mTop, mScreenWidth, mParent.getTotalDragDistance());drawSky(canvas);drawSun(canvas);drawTown(canvas);canvas.restoreToCount(saveCount);}
其中将画布移动到了mtop的位置,再看之前初始化的时候将其赋值为mTop = -mParent.getTotalDragDistance();,
canvas.clipRect(0, -mTop, mScreenWidth, mParent.getTotalDragDistance());
这里一开始是截取了一个高度为0的矩形,随着move慢慢变大,后面的draw就只能在这上面操作。重要的就讲完了,其他的包括怎么根据百分比来改变属性,这些其实可以自己发挥实现自己的效果。
示例代码:
mPullToRefreshView = (PullToRefreshView) findViewById(R.id.pull_to_refresh);mPullToRefreshView.setOnRefreshListener(new PullToRefreshView.OnRefreshListener() { @Override public void onRefresh() { mPullToRefreshView.postDelayed(new Runnable() { @Override public void run() { mPullToRefreshView.setRefreshing(false); } }, REFRESH_DELAY); } });
0 0
- android 控件 下拉刷新 phoenix 带源码分析
- 下拉刷新Phoenix分析
- Android SwipeRefreshLayout下拉刷新控件源码简单分析
- android 下拉刷新源码分析
- Android自带的下拉刷新控件
- Android下拉刷新控件SwipeRefreshLayout源码浅析
- Android下拉刷新控件SwipeRefreshLayout源码浅析
- Android下拉刷新控件SwipeRefreshLayout源码浅析
- 解读Google官方SwipeRefreshLayout控件源码,带你揭秘Android下拉刷新的实现原理
- 解读Google官方SwipeRefreshLayout控件源码,带你揭秘Android下拉刷新的实现原理
- 开源Android-PullToRefresh下拉刷新源码分析
- 开源Android-PullToRefresh下拉刷新源码分析
- 开源Android-PullToRefresh下拉刷新源码分析
- 使用Android系统自带的下拉刷新控件
- android 下拉刷新控件
- Android 下拉刷新控件
- Android下拉刷新控件
- 5.3.2 开源Android-PullToRefresh下拉刷新源码分析
- uvalive4987
- 怎样找到项目打包后的文件
- Leetcode 258 Add Digits
- const 指针与指针指向的内容为const
- 【问题解决】解决Yum安装中出现的mirrorlist.txt错误
- android 控件 下拉刷新 phoenix 带源码分析
- 自定义控件:圆形进度条的实现
- 高德地图SDK中AMapNaviView崩溃
- 两种方法求两个数的最大公约数和最小公倍数--C语言
- CADisplay及定时器使用Second
- linux 开机自动启动 shell
- PHP关于VC11,VC9,VC6以及Thread Safe和Non Thread Safe版本选择的问题
- git撤销本地修改与回退版本
- 偏函数