Android贝塞尔曲线粘性下拉刷新仿QQ粘性拉动效果

效果:

这里写图片描述

主要涉及到贝塞尔曲线 自定义View 属性动画
代码看注释 写得挺清楚了

attr_pull.xml

<resources>
    <declare-styleable name="TouchPullView">
        <attr name="pColor" format="color"/>
        <attr name="pRadius" format="dimension"/>
        <attr name="pDragHeight" format="dimension"/>
        <attr name="pTangentAngle" format="integer"/>
        <attr name="pTargetWidth" format="dimension"/>
        <attr name="pTargetGravityHeight" format="dimension"/>
        <attr name="pContentDrawable" format="reference"/>
        <attr name="pContentDrawableMargin" format="dimension"/>
    </declare-styleable>

创建shape: ic_draw_circle.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval"
    >
<solid android:color="#80ffffff"/>
</shape>

创建TouchPullView继承View

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.nfc.Tag;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.view.animation.PathInterpolatorCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;

/**
 * Created by wxy on 2017/12/8.
 */

public class TouchPullView extends View {
    //圆的画笔
    private Paint mCirclePaint;
    //圆的半径
    private float mCircleRadius = 50;
    private float mCirclePaintX, mCirclePaintY;

    private float mProgress;
    //可拖动的高度
    private int mDragHright = 300;

    //目标宽度
    private int mTargetWidth = 400;
    //贝塞尔曲线的路径以及画笔
    private Path mPath = new Path();
    private Paint mPathPaint;
    //重心点最终高度,决定控制点的y坐标
    private int mTargetGravityHeight = 10;
    //角度变换 0 — 135度
    private int mTargentAngle = 105;

    private Interpolator mProgressInterpolator = new DecelerateInterpolator();
    private Interpolator mTanentAngleInterpolator;

    private Drawable mContent = null;
    private int mContentMargin = 0;

    public TouchPullView(Context context) {
        super(context);
        init(null);
    }


    public TouchPullView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    public TouchPullView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }


    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public TouchPullView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs);
    }

    //初始化
    private void init( AttributeSet attrs) {
        //得到用户设置的参数
        final Context context = getContext();
        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.TouchPullView,0,0);
        int color = array.getColor(R.styleable.TouchPullView_pColor,0x20000000);

        mCircleRadius = array.getDimension(R.styleable.TouchPullView_pRadius,mCircleRadius);
        mDragHright = array.getDimensionPixelOffset(R.styleable.TouchPullView_pDragHeight,mDragHright);
        mTargentAngle = array.getInteger(R.styleable.TouchPullView_pTangentAngle,100);
        mTargetWidth = array.getDimensionPixelOffset(R.styleable.TouchPullView_pTargetWidth,mTargetWidth);
        mTargetGravityHeight = array.getDimensionPixelOffset(R.styleable.TouchPullView_pTargetGravityHeight,mTargetGravityHeight);

        mContent = array.getDrawable(R.styleable.TouchPullView_pContentDrawable);
        mContentMargin = array.getDimensionPixelOffset(R.styleable.TouchPullView_pContentDrawableMargin,0);

        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        //抗锯齿
        p.setAntiAlias(true);
        //防抖动
        p.setDither(true);
        //设置为填充模式
        p.setStyle(Paint.Style.FILL);
        p.setColor(0xFFFF4081);
        mCirclePaint = p;
        //初始化路径部分画笔
        p = new Paint(Paint.ANTI_ALIAS_FLAG);
        //抗锯齿
        p.setAntiAlias(true);
        //防抖动
        p.setDither(true);
        //设置为填充模式
        p.setStyle(Paint.Style.FILL);
        p.setColor(0xFFFF4081);
        mPathPaint = p;

        //切角路径差值器
        mTanentAngleInterpolator = PathInterpolatorCompat.create(
                (mCircleRadius * 2.0f) / mDragHright,
                90.0f / mTargentAngle
        );


        //销毁
        array.recycle();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //进行基础坐标参数系改变
        int count = canvas.save();
        float tranX = (getWidth() - getValueByLine(getWidth(), mTargetWidth, mProgress)) / 2;
        canvas.translate(tranX, 0);

        //画贝塞尔曲线
        canvas.drawPath(mPath, mPathPaint);

        //画圆
        canvas.drawCircle(mCirclePaintX, mCirclePaintY, mCircleRadius, mCirclePaint);

        Drawable drawable = mContent;
        if (drawable!=null){

            canvas.save();
            //剪切矩形区域
            canvas.clipRect(drawable.getBounds());
              //绘制回执Drawable
            drawable.draw(canvas);

            canvas.restore();
        }

        canvas.restoreToCount(count);
    }

    /**
     * 进行测量时触发
     * 参数有两个意图:1.最大限度值 2.确信值 要设置的当前值
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);


        int iWidth = (int) (2 * mCircleRadius + getPaddingLeft() + getPaddingRight());
        int iHeight = (int) ((mDragHright * mProgress + 0.5f) + getPaddingTop() + getPaddingBottom());

        int mesureWidth, mesureHeight;

        if (widthMode == MeasureSpec.EXACTLY) {
            //确定的值
            mesureWidth = width;
        } else if (widthMode == MeasureSpec.AT_MOST) {

            //最多
            mesureWidth = Math.min(iWidth, width);
        } else {
            mesureWidth = iWidth;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            //确定的值
            mesureHeight = height;
        } else if (heightMode == MeasureSpec.AT_MOST) {

            //最多
            mesureHeight = Math.min(iHeight, height);
        } else {
            mesureHeight = iHeight;
        }
        //设置测量的宽度和高度
        setMeasuredDimension(mesureWidth, mesureHeight);

    }



    /**
     * 当大小改变时触发
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //当高度变化时 路径更新
        upadatePathLayout();
    }

    /**
     * 设置进度
     *
     * @param progress
     */
    public void setProgress(float progress) {
        Log.e("TAG:", "P:" + progress);
        //设置进度
        mProgress = progress;
        //请求重新测量
        requestLayout();
    }

    /**
     * 更新路径等相关操作
     */
    private void upadatePathLayout() {
        //获取进度
        final float progress = mProgressInterpolator.getInterpolation(mProgress);


        //可绘制区域高度宽度
        final float w = getValueByLine(getWidth(), mTargetWidth, mProgress);
        final float h = getValueByLine(0, mDragHright, mProgress);
        //x对称轴的参数 圆的圆心X
        final float cPointX = w / 2;
        //圆的半径
        final float cRadius = mCircleRadius;
        //圆的圆心Y坐标
        final float cPointY = h - cRadius;
        //控制点结束Y的值
        final float endConterolY = mTargetGravityHeight;

        //更新圆的坐标
        mCirclePaintX = cPointX;
        mCirclePaintY = cPointY;
        //路径
        final Path path = mPath;
        //复位操作
        path.reset();
        path.moveTo(0, 0);


        //左边部分的结束点和控制点
        float lEndPointX, lEndPointY;
        float lControlPointX, lControlPointY;
        float angle =mTargentAngle* mTanentAngleInterpolator.getInterpolation(progress);
        //获取当前切线的弧度
        double radion = Math.toRadians(angle);
        float x = (float) (Math.sin(radion) * cRadius);
        float y = (float) (Math.cos(radion) * cRadius);

        //结束点
        lEndPointX = cPointX - x;
        lEndPointY = cPointY + y;
        //控制点的Y轴变化
        lControlPointY = getValueByLine(0, endConterolY, progress);
        //控制点与结束点之间的高度
        float tHeight = lEndPointY - lControlPointY;
        //控制点与X的坐标距离
        float tWidth = (float) (tHeight / Math.tan(radion));
        lControlPointX = lEndPointX - tWidth;

        //左边的贝塞尔曲线
        path.quadTo(lControlPointX, lControlPointY, lEndPointX, lEndPointY);
        //链接到右边
        path.lineTo(cPointX + (cPointX - lEndPointX), lEndPointY);
        //右边的贝塞尔曲线
        path.quadTo(cPointX + cPointX - lControlPointX, lControlPointY, w, 0);
        //更新内容部分Drawble
        updateContentLayout(cPointX,cPointY,cRadius);
    }

    /**
     * 对内容部分进行测量并设置
     * @param cx 圆心X
     * @param cy 圆心Y
     * @param radius 半径
     */
    private void updateContentLayout(float cx,float cy,float radius){
        Drawable drawable = mContent;
        if (drawable!=null){
            int margin = mContentMargin;
            int l = (int) (cx - radius+margin);
            int r = (int) (cx+radius-margin);
            int t = (int) (cy-radius+margin);
            int b = (int) (cy+radius-margin);
            drawable.setBounds(l,t,r,b);

        }


    }


    /**
     * 获取当前值
     *
     * @param start    起始值
     * @param end      结束值
     * @param progress 进度值
     * @return 当前进度的值
     */
    private float getValueByLine(float start, float end, float progress) {

        return start + (end - start) * progress;
    }

    private ValueAnimator valueAnimator;

    /**
     * 添加释放操作
     */
    public void release() {
        if (valueAnimator == null) {
            ValueAnimator animator = ValueAnimator.ofFloat(mProgress, 0f);
            animator.setInterpolator(new DecelerateInterpolator());
            animator.setDuration(400);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    Object val = animation.getAnimatedValue();
                    if (val instanceof Float) {
                        setProgress((Float) val);
                    }

                }
            });
            valueAnimator = animator;
        } else {
            valueAnimator.cancel();
            valueAnimator.setFloatValues(mProgress, 0f);
        }
        valueAnimator.start();
    }


}

MainActivity

import android.os.Bundle;
import android.support.constraint.ConstraintLayout;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;

public class MainActivity extends AppCompatActivity {
    private static final float TOUCH_MOVE_MAX_Y = 600;
    private TouchPullView touchPull;
    private ConstraintLayout activity_main;
    private float mTouMoveStartY = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();

    }

    private void initView() {
        touchPull = (TouchPullView) findViewById(R.id.touchPull);
        activity_main = (ConstraintLayout) findViewById(R.id.activity_main);
        activity_main.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getActionMasked();
                switch (action) {
                    //按下
                    case MotionEvent.ACTION_DOWN:
                        mTouMoveStartY = event.getY();
                        return true;
                    //移动
                    case MotionEvent.ACTION_MOVE:
                        float moveSize = event.getY();
                        //如果大于按下的位置 说明是往下拉
                        if (moveSize >= mTouMoveStartY) {
                            //获取拉取的距离
                            float progress = moveSize >= TOUCH_MOVE_MAX_Y ? 1 : moveSize / TOUCH_MOVE_MAX_Y;
                            touchPull.setProgress(progress);
                        }
                        return true;


                    default:
                        touchPull.release();
                }

                return false;
            }
        });

    }
}

布局文件:

<android.support.constraint.ConstraintLayout 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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.wxy.jishudiancontent.MainActivity">

    <com.example.wxy.jishudiancontent.TouchPullView
        android:id="@+id/touchPull"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:pContentDrawable="@drawable/ic_draw_circle"
        app:pContentDrawableMargin="2dp"
        app:pDragHeight="120dp"
        app:pColor="@color/colorAccent"
        app:pRadius="25dp"
        app:pTangentAngle="110"
        app:pTargetGravityHeight="4dp"
        app:pTargetWidth="200dp"
        />


</android.support.constraint.ConstraintLayout>

OK!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值