Android View 下拉刷新之头部效果自定义 [水]

来源:互联网 发布:红警3起义时刻兵种数据 编辑:程序博客网 时间:2024/05/02 00:27

尊重原创转载请注明:http://blog.csdn.net/bfbx5173/article/details/49814551

Rick    镇楼

好久没来博客耍耍了。俗话说学到的东西要多复习,不然就会忘记,今日无聊来一发。

首先呢,这个下拉刷新基于开源框架 android-Ultra-Pull-To-Refresh

在build.gradle中

compile 'in.srain.cube:ultra-ptr:1.0.11'

so - 尚不清楚android-Ultra-Pull-To-Refresh的朋友[ 戳我查看 android-Ultra-Pull-To-Refresh 源码解析]

这是 Ultra中提供的常规默认刷新的下拉头PtrClassicDefaultHeader。我们来看看源码

public class PtrClassicDefaultHeader extends FrameLayout implements PtrUIHandler {    // 省略所有代码...}

没错,只不过是一个实现了接口 PtrUIHandler的View而已。那PtrUIHandler 定义了什么呢?

public interface PtrUIHandler {    void onUIReset(PtrFrameLayout var1);    void onUIRefreshPrepare(PtrFrameLayout var1);    void onUIRefreshBegin(PtrFrameLayout var1);    void onUIRefreshComplete(PtrFrameLayout var1);    void onUIPositionChange(PtrFrameLayout var1, boolean var2, byte var3, PtrIndicator var4);}

看样子是定义了一些“时机”,在这些“时机”下我们的这个这个view可以做一些羞羞的事情。
管它的呢,不管怎么分析,总是要写的。既然是view,那么先走上。

感觉下需要的高度,蓝色的背景和白色的Loading对应的位置、大小。然后把这个画粗来。
定义 PtrMDHeader

public class PtrMDHeader extends View implements PtrUIHandler {    private final Paint paint = new Paint();    private final RectF ovalLoading = new RectF();    private final RectF ovalBlueBack = new RectF();    private int mWidth;    private int mHeight;    private int centerX;    private int centerY;    private int mLoadingStrokeWidth;    private int radiusBlue;    private int radiusWhite;    public PtrMDHeader(Context context) {        this(context, null);    }    public PtrMDHeader(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        final float scale = getResources().getDisplayMetrics().density;        // 先拿60dip的高度耍耍        int heightSize = MeasureSpec.makeMeasureSpec((int) (60 * scale + 0.5f), View.MeasureSpec.EXACTLY);        super.onMeasure(widthMeasureSpec, heightSize);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        mWidth = w; // PtrMDHeader的宽度        mHeight = h; // PtrMDHeader的高度        centerX = mWidth / 2; // X轴中心点        centerY = mHeight / 2; // Y轴中心点        radiusBlue = mHeight / 4; // 蓝色背景半径        radiusWhite = radiusBlue / 2; // 白色Loading半径        mLoadingStrokeWidth = radiusBlue / 8; // 白色Loading尺寸        /* 白色Loading所在矩形区域 */        ovalLoading.left = centerX - radiusWhite;        ovalLoading.right = centerX + radiusWhite;        ovalLoading.top = centerY - radiusWhite;        ovalLoading.bottom = centerY + radiusWhite;        /** 蓝色扇形所在矩形区域*/        ovalBlueBack.left = centerX - radiusBlue;        ovalBlueBack.right = centerX + radiusBlue;        ovalBlueBack.top = centerY - radiusBlue;        ovalBlueBack.bottom = centerY + radiusBlue;    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        canvas.drawColor(0xffcccccc);        paint.setAntiAlias(true);        // 蓝色背景        paint.setColor(0xFF5AB1E8);        paint.setStyle(Paint.Style.FILL);        canvas.drawCircle(centerX, centerY, radiusBlue, paint);        // 白色loading        paint.setColor(0xFFFFFFFF);        paint.setStyle(Paint.Style.STROKE);        paint.setStrokeWidth(mLoadingStrokeWidth);        canvas.drawArc(ovalLoading, 90, 270, false, paint);    }    @Override    public void onUIReset(PtrFrameLayout ptrFrameLayout) {        Log.d("PtrMDHeader", "onUIReset");    }    @Override    public void onUIRefreshPrepare(PtrFrameLayout ptrFrameLayout) {        Log.d("PtrMDHeader", "onUIRefreshPrepare");    }    @Override    public void onUIRefreshBegin(PtrFrameLayout ptrFrameLayout) {        Log.d("PtrMDHeader", "onUIRefreshBegin");    }    @Override    public void onUIRefreshComplete(PtrFrameLayout ptrFrameLayout) {        Log.d("PtrMDHeader", "onUIRefreshComplete");    }    @Override    public void onUIPositionChange(PtrFrameLayout ptrFrameLayout, boolean b, byte b1, PtrIndicator ptrIndicator) {        Log.d("PtrMDHeader", "onUIPositionChange b:" + b + ", b1:" + b1 + "percent:" + ptrIndicator.getCurrentPercent());    }}
东西写完了,还需要一些配置,在xml中:

<in.srain.cube.views.ptr.PtrFrameLayout        android:id="@+id/lay_ptr_frame"        android:layout_width="match_parent"        android:layout_height="match_parent">        <LinearLayout            android:id="@+id/store_house_ptr_image_content"            android:layout_width="match_parent"            android:layout_height="match_parent"            android:background="#FFFFFF"            android:gravity="center_horizontal">            <TextView                android:id="@+id/tx_test"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_gravity="center" />            <ImageView                android:contentDescription="@string/app_name"                android:id="@+id/img_test"                android:layout_width="200dip"                android:layout_height="200dip"                android:layout_gravity="bottom|center_horizontal"                android:background="#ccc" />        </LinearLayout></in.srain.cube.views.ptr.PtrFrameLayout>

然后在activity中

store_house_ptr_image_content = findViewById(R.id.store_house_ptr_image_content);lay_ptr_frame = (PtrFrameLayout) findViewById(R.id.lay_ptr_frame);final PtrMDHeader header = new PtrMDHeader(getContext());lay_ptr_frame.setHeaderView(header);lay_ptr_frame.addPtrUIHandler(header);lay_ptr_frame.setPtrHandler(new PtrHandler() {    @Override    public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {        return PtrDefaultHandler.checkContentCanBePulledDown(frame, store_house_ptr_image_content, header);    }    @Override    public void onRefreshBegin(final PtrFrameLayout frame) {        // 模仿延时3000毫秒        postDelayed(new Runnable() {            @Override            public void run() {                frame.refreshComplete();            }        }, 3000);    }});

好配菜都准备完成了,看下效果。


6666666,似乎少了很多东西。不过万事开头难,头开出来了后面就慢慢来呗。

继续分析:
少了很多状态,这个应该有很多状态,不同的状态下显示不一样。so

    /** 状态-闲置 */    public static final byte STATE_IDLE = 0;    /** 状态-下拉去刷新 */    public static final byte STATE_PULL_TO_REFRESH = 1;    /** 状态-释放去刷新 */    public static final byte STATE_RELEASE_TO_REFRESH = 2;    /** 状态-刷新中 */    public static final byte STATE_REFRESHING = 3;    /** 状态-刷新完成 */    public static final byte STATE_COMPLETED = 4;    /** 当前状态 */    private byte mState = STATE_IDLE;

应该是这几个状态没跑了。
细心的朋友可以发现在之前的代码中,偷偷打了写log。

当我们下拉的时候onUIPositionChange(PtrFrameLayout ptrFrameLayout, boolean b, byte b1, PtrIndicator ptrIndicator)就会一直走,而ptrIndicator中携带了位置等一些信息。
在这里我们可以把手指拖拽的2个状态[下拉去刷新-释放去刷新]补上

private float currPercent;@Overridepublic void onUIPositionChange(PtrFrameLayout ptrFrameLayout, boolean b, byte b1, PtrIndicator ptrIndicator) {    Log.d("PtrMDHeader", "onUIPositionChange b:" + b + ", b1:" + b1 + "percent:" + ptrIndicator.getCurrentPercent());    currPercent = ptrIndicator.getCurrentPercent();    // 下拉刷新?释放刷新?下拉刷新?释放刷新?下拉..    mState = currPercent < 1f ? STATE_PULL_TO_REFRESH : STATE_RELEASE_TO_REFRESH;    invalidate();}
这里currPercent的值会随手指拖拽的变化而变化,PtrMDHeader 的状态也根据currPercent的变化而变化。
然后就invalidate()啦,那么代码转移到onDraw中:

@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    // 临时加上背景灰色利于分析    canvas.drawColor(0xffcccccc);    paint.setAntiAlias(true);        switch (mState){        case STATE_PULL_TO_REFRESH: // 刚刚下拉中,还木有到释放刷新的程度。执行这个case        default:            break;        case STATE_RELEASE_TO_REFRESH: // 下拉的距离够了,手指可以松开释放刷新了。不松就执行这个case            // 蓝色背景            paint.setColor(0xFF5AB1E8);            paint.setStyle(Paint.Style.FILL);            canvas.drawCircle(centerX, centerY, radiusBlue, paint);            // 白色loading            paint.setColor(0xFFFFFFFF);            paint.setStyle(Paint.Style.STROKE);            paint.setStrokeWidth(mLoadingStrokeWidth);            canvas.drawArc(ovalLoading, 90, 270, false, paint);            break;    }}

来,哥图多

这里可以看出来,在STATE_PULL_TO_REFRESH中也做了2段动画。第一段是蓝色背景以一个扇形慢慢补全。第二段是白色Loading的补全。

    /** 初始 */    private static final float STEP_IDLE = 0f;    /** 第一段和第二段分割界线 */    private static final float STEP_HALF_OF_PULL = 0.7f;    /** 下拉刷新和释放刷新分割界线 */    private static final float STEP_RELEASE_TO_REFRESH = 1.0f;

case STATE_PULL_TO_REFRESH:default:    if (currPercent < STEP_HALF_OF_PULL) {        // 第一段 背景以扇形的方式扇形出来        final float p1 = (currPercent - STEP_IDLE) / (STEP_HALF_OF_PULL - STEP_IDLE);        paint.setColor(Color.argb((int) (255 * p1), 90, 177, 232));        paint.setStyle(Paint.Style.FILL);        canvas.drawArc(ovalBlueBack, -90, 360 * p1, true, paint);    } else {        // 第一段 白色loading以旋转的方式出来        final float p2 = (currPercent - STEP_HALF_OF_PULL) / (STEP_RELEASE_TO_REFRESH - STEP_HALF_OF_PULL);        paint.setColor(0xFF5AB1E8);        paint.setStyle(Paint.Style.FILL);        canvas.drawCircle(centerX, centerY, radiusBlue, paint);        paint.setColor(Color.argb((int) (255 * p2), 255, 255, 255));        paint.setStyle(Paint.Style.STROKE);        paint.setStrokeWidth(mLoadingStrokeWidth);        canvas.drawArc(ovalLoading, -90 + 180 * p2, 270, false, paint);    }break;

第一段扇形的效果看不见加个偏移。

还记得这个效果吗~ 在 SwitchView中用过哟

case STATE_PULL_TO_REFRESH:default:    final float p = (currPercent - STEP_IDLE) / (STEP_RELEASE_TO_REFRESH - STEP_IDLE);    canvas.save();    canvas.translate(0, (radiusBlue * 1.8f) * (1 - p));    // ... 省略刚才的代码    canvas.restore();break;

 

有没有觉得左边[做了偏移]效果比右边[没有偏移]好呀

好我们继续研究刷新

把刷新拆解成这4个状态。红色表示起点,蓝色表示重点。用画圆弧的api[drawArc]来做。
比如状态[1]到状态[2],起点经过了360度,而圆弧长度由270度下降到30度。依次分析那么:

private float mAnimPercent; // 动画进度,控制刷新中的显示private ValueAnimator valueAnimator;private final AccelerateDecelerateInterpolator adi = new AccelerateDecelerateInterpolator();private void doRefreshingAnim() {    if (valueAnimator != null) {        valueAnimator.cancel();        valueAnimator = null;    }    valueAnimator = ValueAnimator.ofFloat(0f, 1f, 2f, 3f,4f);    valueAnimator.setRepeatMode(ValueAnimator.RESTART);     valueAnimator.setRepeatCount(ValueAnimator.INFINITE); // 无限重复    valueAnimator.setDuration(2000);    valueAnimator.setInterpolator(adi);    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {        @Override        public void onAnimationUpdate(ValueAnimator animation) {            mAnimPercent = (Float) animation.getAnimatedValue();            invalidate();        }    });    mState = STATE_REFRESHING;    valueAnimator.start();}switch (mState) {    case STATE_REFRESHING:        // 蓝色背景        paint.setColor(0xFF5AB1E8);        paint.setStyle(Paint.Style.FILL);        canvas.drawCircle(centerX, centerY, radiusBlue, paint);        // 白色loading        paint.setColor(0xFFFFFFFF);        paint.setStyle(Paint.Style.STROKE);        paint.setStrokeWidth(mLoadingStrokeWidth);        final int s = (int) mAnimPercent;        final float mp;        switch (s) {            case 0:                mp = mAnimPercent;                canvas.drawArc(ovalLoading, 90 + (360 * mp), 30 + 240 * (1 - mp), false, paint);                break;            case 1:                mp = mAnimPercent - 1;                canvas.drawArc(ovalLoading, 90 + (180 * mp), 30 + 240 * mp, false, paint);                 break;            case 2:                mp = mAnimPercent - 2;                canvas.drawArc(ovalLoading, -90 + (360 * mp), 30 + 240 * (1 - mp), false, paint);                break;            case 3:                mp = mAnimPercent - 3;                canvas.drawArc(ovalLoading, -90 + (180 * mp), 30 + 240 * mp, false, paint);                break;        }    break;}

没错,case 0,1,2,3 分别表示图中4个步骤。

@Overridepublic void onUIPositionChange(PtrFrameLayout ptrFrameLayout, boolean b, byte b1, PtrIndicator ptrIndicator) {    Log.d("PtrMDHeader", "onUIPositionChange b:" + b + ", b1:" + b1 + "percent:" + ptrIndicator.getCurrentPercent());    currPercent = ptrIndicator.getCurrentPercent();    if (mState != STATE_REFRESHING) {        // 刷新的状态。invalidate的逻辑由valueAnimator来控制。        mState = currPercent < 1f ? STATE_PULL_TO_REFRESH : STATE_RELEASE_TO_REFRESH;        invalidate();        }}@Overridepublic void onUIRefreshBegin(PtrFrameLayout ptrFrameLayout) {    Log.d("PtrMDHeader", "onUIRefreshBegin");    doRefreshingAnim();}
最后在onUIRefreshBegin启动这段动画


当然这里想怎么搞就怎么搞~ 画布在手天下我有。


最后当刷新完成的时候

// 刷新完成时,刷新成功或失败的bmpprivate Bitmap bmpSuccess, bmpFail;@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {    super.onSizeChanged(w, h, oldw, oldh);    BitmapFactory.Options opt = new BitmapFactory.Options();    opt.outWidth = radiusBlue;    opt.outHeight = radiusBlue;    bmpSuccess = BitmapFactory.decodeResource(getResources(), R.mipmap.ptr_header_md_success, opt);    bmpFail = BitmapFactory.decodeResource(getResources(), R.mipmap.ptr_header_md_fail, opt);}// 根据刷新结果显示不同的内容private Bitmap getRefreshResultBitmap() {    return refreshResult ? bmpSuccess : bmpFail;}private final OvershootInterpolator oi = new OvershootInterpolator();private void doCompleteAnim() {    if (valueAnimator != null) {        valueAnimator.cancel();        valueAnimator = null;    }    valueAnimator = ValueAnimator.ofFloat(0f, 1f);    valueAnimator.setDuration(400);    valueAnimator.setInterpolator(oi);    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {        @Override        public void onAnimationUpdate(ValueAnimator animation) {            mAnimPercent = (Float) animation.getAnimatedValue();            invalidate();        }    });    mState = STATE_COMPLETED;    valueAnimator.start();}switch (mState) {    case STATE_COMPLETED:        paint.setColor(0xFF5AB1E8);        paint.setStyle(Paint.Style.FILL);        canvas.drawCircle(centerX, centerY, radiusBlue, paint);        canvas.save();        canvas.scale(mAnimPercent, mAnimPercent, centerX, centerY);        final Bitmap bmpResult = getRefreshResultBitmap();        canvas.drawBitmap(bmpResult, centerX - bmpResult.getWidth() / 2, centerY - bmpResult.getHeight() / 2, paint);        canvas.restore();    break;}/** 提供一个公开的接口来调用  refreshResult表示刷新结果 */public void refreshComplete(boolean refreshResult, final PtrFrameLayout frame) {    this.refreshResult = refreshResult;    doCompleteAnim();    postDelayed(new Runnable() {        @Override        public void run() {            frame.refreshComplete();        }    }, 600);}

最后在activity中调用
lay_ptr_frame.setPtrHandler(new PtrHandler() {    @Override    public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {        return PtrDefaultHandler.checkContentCanBePulledDown(frame, store_house_ptr_image_content, header);    }    @Override    public void onRefreshBegin(final PtrFrameLayout frame) {        postDelayed(new Runnable() {            @Override            public void run() {                // 模仿延时3000毫秒                boolean refreshResult = true;                header.refreshComplete(refreshResult, frame);            }        }, 3000);    }});
分别给refreshResult传入true,false

当然这2个图片可以替换成随意内容。
貌似刷新完成后,header收起的时候显示不对~


@Overridepublic void onUIPositionChange(PtrFrameLayout ptrFrameLayout, boolean b, byte b1, PtrIndicator ptrIndicator) {    currPercent = ptrIndicator.getCurrentPercent();    if (mState != STATE_REFRESHING && mState != STATE_COMPLETED) {        // 状态为刷新中或者刷新完成的时候,不根据currPercent来更新UI。        mState = currPercent < STEP_RELEASE_TO_REFRESH ? STATE_PULL_TO_REFRESH : STATE_RELEASE_TO_REFRESH;        invalidate();    }}

最后刷新完成重置一下状态

@Overridepublic void onUIReset(PtrFrameLayout ptrFrameLayout) {    Log.d("PtrMDHeader", "onUIReset");    mState = STATE_IDLE;}

好了,到此大功告成! 一样才300行代码。

[逼格提升入口]戳我获得完整PtrMDHeader源码[附含详细使用教程]


fork源码记得点赞Star哟,仔细阅读README.md文件。你会发现使用起来so easy。


~




7 0
原创粉丝点击