Android自定义下拉刷新

Posted 我想月薪过万

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义下拉刷新相关的知识,希望对你有一定的参考价值。

效果展示

Android自定义下拉刷新

第一步:编写自定义View 的java代码

package com.wust.mycaryaokong.myUI;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.icu.text.SymbolTable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;

import androidx.annotation.Nullable;

import com.wust.mycaryaokong.R;

import java.util.Timer;
import java.util.TimerTask;

/**
 * ClassName: refreshHead <br/>
 * Description: <br/>
 * date: 2021/5/21 21:11<br/>
 *
 * @author yiqi<br />
 * @since JDK 1.8
 */
public class refreshHead extends ViewGroup {

    private Bitmap mBitmap;
    private Paint mImgPaint;
    private int mScreenWidth;
    private Paint mRingPaint;
    private int mWidth;
    private int mHeight;
    private float mRate = 0;
    private int mRingHeight = 120;
    private int mStartY;
    private int mHeadHeight = 0;
    private stateCallBack mStateCallBack;
    private Paint mTextPaint;
    private Timer mTimer = new Timer();
    private TimerTask mTimerTask;
    private boolean refreshState = false;
    private long mRefreshTime = 1000;

    public refreshHead(Context context) {
        this(context, null);
    }

    public refreshHead(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public refreshHead(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.refreshHead);
        BitmapDrawable mDrawable = (BitmapDrawable) typedArray.getDrawable(R.styleable.refreshHead_refreshbg);
        mBitmap = mDrawable.getBitmap();
        //注意回收,别忘记了
        typedArray.recycle();

        //获取屏幕宽高
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Point point = new Point();
        wm.getDefaultDisplay().getSize(point);
        mScreenWidth = point.x;
        //初始化画笔
        mImgPaint = initPaint(0, 0);
        mRingPaint = initPaint(Color.parseColor("#ff0000"), 10);
        mTextPaint = initPaint(Color.BLUE, 8);
    }

    private Paint initPaint(int color, int PaintWidth) {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setDither(true);
        if (color != 0) paint.setColor(color);
        if (PaintWidth != 0) paint.setTextSize(PaintWidth);
        return paint;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(mWidth, mHeight);
    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {

    }
 
    //ViewGroup不会执行 onDraw ,看过源码的都知道
    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        drawPicture(canvas);
        //保存画布状态,因为画布 旋转 缩放 移动是不可逆的
        canvas.save();
        canvas.scale(mRate, mRate, mWidth / 2, mHeadHeight / 2);
        canvas.rotate(180 * mRate, mWidth / 2, mHeadHeight / 2);
        if (!refreshState){
            if (mRate == 1.0f) {
                //还原画布状态
                canvas.restore();
                drawText(canvas,"松手刷新");
            } else if (mRate < 1) {
                drawRing(canvas);
            }
        }else {
            //还原画布状态
            canvas.restore();
            drawText(canvas,"刷新中...");
        }
    }

    private void drawText(Canvas canvas,String text) {
        mTextPaint.setTextSize(40);
        //获取文字基线
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        int dy = (int) ((fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom);
        int baseline = mHeadHeight / 2 + dy;
        //获取文字宽度
        Rect bounds = new Rect();
        mTextPaint.getTextBounds(text, 0, text.length(), bounds);
        int dx = bounds.width() / 2;
        int x = mScreenWidth / 2 - dx;
        canvas.drawText(text, x, baseline, mTextPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mStartY = (int) event.getY();
                System.out.println("ACTION_DOWN->" + mStartY);
                break;
            case MotionEvent.ACTION_MOVE:
                int endY = (int) event.getY();
                int dy = endY - mStartY;
                mHeadHeight = dy;
                mRate = dy * 1.0f / 200;
                if (mHeadHeight > 200) {
                    mHeadHeight = 200;
                    mRate = 1.0f;
                    System.out.println("ACTION_MOVE ->" + mRate);
                    invalidate();
                    if (mStateCallBack != null) {
                        //调用到底的接口
                        mStateCallBack.onBottom();
                    }
                    return false;
                }
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                if (mRate == 1.0f) {
                    refreshState = true;
                    invalidate();
                    //调用刷新的借口
                    mStateCallBack.refreshing();
                    mTimerTask = new TimerTask() {
                        @Override
                        public void run() {
                            mHeadHeight = 0;
                            mRate = 0;
                            refreshState = false;
                            invalidate();
                            //调用刷新完成接口
                            mStateCallBack.refreshed();
                        }
                    };
                    mTimer.schedule(mTimerTask,mRefreshTime);
                }else {
                    mHeadHeight = 0;
                    mRate = 0;
                    invalidate();
                }
                break;
        }
        return true;
    }

    private void drawRing(Canvas canvas) {
        if (mRate <= 0.2) {
            mRingPaint.setColor(Color.TRANSPARENT);
        } else {
            mRingPaint.setColor(Color.RED);
        }
        Path path = new Path();
        path.moveTo(mWidth / 2, mHeadHeight / 2 + 31);
        path.lineTo(mWidth / 2 + 50, mHeadHeight / 2 - 31);
        path.lineTo(mWidth / 2 - 50, mHeadHeight / 2 - 31);
        path.close();
        canvas.drawPath(path, mRingPaint);
    }

    private void drawPicture(Canvas canvas) {
        Rect src = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
        Rect dst = new Rect(0, 0, mScreenWidth, mHeadHeight);
        canvas.drawBitmap(mBitmap, src, dst, mImgPaint);
    }

    //编写接口,规范用户
    private interface stateCallBack {
        void onBottom();
        void refreshing();
        void refreshed();
    }

    //监听刷新状态回调 ,把接口暴露出去,让用户实现
    public void setOnStateCallBack(stateCallBack stateCallBack) {
        if (this.mStateCallBack == null) {
            this.mStateCallBack = stateCallBack;
        }
    }
    //设置刷新时间
    public void setRefreshTime(long duration){
        this.mRefreshTime = duration;
    }
}

第二步:布局中使用

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.wust.mycaryaokong.myUI.refreshHead
        android:id="@+id/rfh_heard"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:refreshbg="@drawable/refreshhead">
    </com.wust.mycaryaokong.myUI.refreshHead>

</FrameLayout>

里面涉及一个自定义属性

第三步:难点讲解

自定义View的基础知识我已经在前面讲解过了,在这里我就不过多赘述了,我主要谈谈我自己在编写的过程中遇到的问题:

1、ViewGroup系统默认是不执行onDraw()的,如何解决呢?

解:请大家参考我写的这篇文章,为什么ViewGroup的onDraw()方法不执行,里面写得很详细

2、手指划太快了,顶部还没滑到自己设定的高度就划不动了?(这个里面也涉及到事件处理,如果你不熟悉,可以先去补补这方面的知识)

解:原因:当布局还在onDraw你  70%预定的头部高度时  你的手指已经滑够了预想的像素点,提前 return false,导致后面的事件不能被消费,这段话对应的代码如下:

3、canvas的旋转、缩放、平移是不可逆的,如何解决?

解:两个命令你用好就可以了,具体在哪里用到了,代码中已经注释了,不过多赘述

canvas.save() //保存当前画布状态到堆中
canvas.restore() //从堆中取出你上次保存的画布状态

后期优化

Android自定义下拉刷新(优化)

以上是关于Android自定义下拉刷新的主要内容,如果未能解决你的问题,请参考以下文章

Android自定义下拉刷新

Android自定义控件--下拉刷新的实现

android 下拉刷新怎么实现

Android自定义下拉刷新动画--仿百度外卖下拉刷新

Android PullToRrefresh 自定义下拉刷新动画 (listviewscrollview等)

Android之自定义控件-下拉刷新