自定義View之粘性下拉刷新效果

PullView.java

/**
 * Created by jian on 17-8-16.
 */

public class PullView extends View {
    //圓的畫筆
    private Paint mCirClePaint;
    //圓的半徑
    private float MCirCleRadius = 50;

    //可拖動的高度
    private int mDragHeight = 300;
    //進度值
    private float mProgress;
    private float mCirClePointX, mCirClePointY;

    //目標寬度
    private int mTargetWidth = 400;
    //貝塞爾曲線的路勁以及畫筆
    private Paint mPathPaint;
    private Path mPath = new Path();
    //重心點最總高度,決定控制點的Y座標
    private int mTarGetGravityHeight = 10;
    //角度變換,0-135度
    private int mTangentAngle = 105;


    private Drawable mContent = null;

    private  int mContentMargin= 0;

    private Interpolator mProgressInterpolator = new DecelerateInterpolator();

    private Interpolator mTangentInterpolator;

    public PullView(Context context) {

        super(context);
        init(null);
    }

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

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

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

    /**
     * 當進行測量的時候觸發
     *
     * @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 measureWidth;
        int measureHeight;
        int iWidth = (int) (2 * MCirCleRadius + getPaddingLeft() + getPaddingRight());
        int iHeight = (int) ((mDragHeight * mProgress + 0.5f) + getPaddingTop() + getPaddingBottom());


        if (widthMode == MeasureSpec.EXACTLY) {
            //卻確的
            measureWidth = width;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            //最多的
            measureWidth = Math.min(iWidth, width);
        } else {
            measureWidth = iWidth;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            //卻確的
            measureHeight = height;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            //最多的
            measureHeight = Math.min(iHeight, height);
        } else {
            measureHeight = iHeight;
        }
        setMeasuredDimension(measureWidth, measureHeight);
    }

    @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(mCirClePointX, mCirClePointY, MCirCleRadius, mCirClePaint);
        Drawable drawable = mContent;
        if(drawable!=null){
            //剪切舉行區域
            Log.i("Tag","----=--lai");
            canvas.save();
            canvas.clipRect(drawable.getBounds());
            //繪製Drawble
            drawable.draw(canvas);
            canvas.restore();
        }

        canvas.restoreToCount(count);
    }

    /**
     * 當大小該表時調用
     *
     * @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);
        //當高度變化時,進行更新
        updatePathLayout();
    }

    /**
     * 初始化方法
     * @param attrs
     */
    private void init(AttributeSet attrs) {

        final Context context = getContext();
        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.PullView);
        int color = array.getColor(R.styleable.PullView_pColor,0xff000000);

        MCirCleRadius = array.getDimension(R.styleable.PullView_pRadius,50);

        mDragHeight = array.getDimensionPixelOffset(R.styleable.PullView_pDragHeight,mDragHeight);
        mTangentAngle =array.getInteger(R.styleable.PullView_pTangentAngle,100);
        mTargetWidth = array.getDimensionPixelOffset(R.styleable.PullView_pTargetWidth,mTargetWidth);
        mTarGetGravityHeight = array.getDimensionPixelOffset(R.styleable.PullView_pTargetGravityHeight,mTarGetGravityHeight);
        //銷燬

        mContent = array.getDrawable(R.styleable.PullView_pContentDrawBle);
        mContentMargin= array.getDimensionPixelOffset(R.styleable.PullView_pConTentDrawableMargin,0);


        array.recycle();
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        //設置看鋸齒
        p.setAntiAlias(true);
        //設置防抖動
        p.setDither(true);
        //設置爲填充方式
        p.setStyle(Paint.Style.FILL);
        p.setColor(color);
        mCirClePaint = p;

        p = new Paint(Paint.ANTI_ALIAS_FLAG);
        //設置看鋸齒
        p.setAntiAlias(true);
        //設置防抖動
        p.setDither(true);
        //設置爲填充方式
        p.setStyle(Paint.Style.FILL);
        p.setColor(color);
        mPathPaint = p;
        //切角路勁插值器
        mTangentInterpolator = PathInterpolatorCompat.create((MCirCleRadius*2.0f)/mDragHeight,90.0f/mTangentAngle);

    }

    public void setProgress(float progress) {
        Log.i("TAGPULLVIEW", progress + "");
        this.mProgress = progress;
        //請求重新繪製
        requestLayout();
    }

    /**
     * 更行我們的路徑
     */
    private void updatePathLayout() {
        //獲取進度
        final float progress = mProgressInterpolator.getInterpolation(mProgress);
        //獲取可繪製區域高度寬度
        final float w = getValueByLine(getWidth(), mTargetWidth, mProgress);
        final float h = getValueByLine(0, mDragHeight, mProgress);
        //圓的中心點X座標
        final float cPointX = w / 2;
        //圓的半徑
        final float cRadius = MCirCleRadius;
        //圓的中心點Y座標
        final float cPointY = h - cRadius;
        //控制點借宿Y的值
        final float endControlY = mTarGetGravityHeight;


        mCirClePointX = cPointX;
        mCirClePointY = cPointY;

        final Path path = mPath;

        //復位操作
        path.reset();
        path.moveTo(0, 0);
        //左邊部分的結束點和控制點
        float lEndPointX, lEndPointY;
        float lControlPointX, lControlPointY;
        //獲取當前切線的弧度
        float angle =mTangentAngle* mTangentInterpolator.getInterpolation(progress);
        double radian = Math.toRadians(angle);

        float x = (float) (Math.sin(radian) * cRadius);

        float y = (float) (Math.cos(radian) * cRadius);


        lEndPointX = cPointX - x;
        lEndPointY = cPointY + y;
        //控制點Y的變化
        lControlPointY = getValueByLine(0, endControlY, progress);
        //控制點與結束點的高度
        float tHeight = lEndPointY - lControlPointY;
        //控制點與X的座標距離
        float tWidth = (float) (tHeight / Math.tan(radian));
        lControlPointX = lEndPointX - tWidth;

        path.quadTo(lControlPointX, lControlPointY, lEndPointX, lEndPointY);

        //鏈接到右邊
        path.lineTo(cPointX + (cPointX - lEndPointX), lEndPointY);
        path.quadTo(cPointX + cPointX - lControlPointX, lControlPointY, w, 0);
        updateContentLayout(cPointX,cPointY,cRadius);
    }

    /**
     * 對內容部分進行測量病設置
     * @param cx
     * @param cy
     * @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 animator) {
                    Object val = animator.getAnimatedValue();
                    if (val instanceof Float) {
                        setProgress((Float) val);
                    }
                }
            });

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

    }
}

ic_draw_cricle.xml

attr_pull.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="PullView">
        <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="pContentDrawBle" format="reference" />
        <attr name="pConTentDrawableMargin" format="dimension" />
    </declare-styleable>
</resources>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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.ying.myview.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="100dp">

        <com.example.ying.myview.PullView
            android:id="@+id/pullView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:pColor="@color/colorAccent"
            app:pConTentDrawableMargin="2dp"
            app:pContentDrawBle="@drawable/ic_draw_cricle"
            app:pDragHeight="100dp"
            app:pRadius="20dp"
            app:pTangentAngle="110"
            app:pTargetGravityHeight="4dp"
            app:pTargetWidth="200dp"


            />

    </LinearLayout>


</LinearLayout>

MainActivity

public class MainActivity extends AppCompatActivity {
    private static final float TOUCH_MOVE_MAX_Y = 600;
    private float mTouchMoveStartX;
    private float mTouchMoveStartY = 0;
    private PullView  pullView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        pullView = (PullView) findViewById(R.id.pullView);
        findViewById(R.id.activity_main).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                int action = motionEvent.getAction();
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        mTouchMoveStartY = motionEvent.getY();
                        return true;

                    case MotionEvent.ACTION_MOVE:
                        float y = motionEvent.getY();
                        if (y > mTouchMoveStartY) {
                            float moveSize = y-mTouchMoveStartY;
                            float progress = moveSize>=TOUCH_MOVE_MAX_Y?1:moveSize/TOUCH_MOVE_MAX_Y;
                            pullView.setProgress(progress);
                        }
                        break;
                    case MotionEvent.ACTION_UP:

                        pullView.release();
                        break;
                }
                return false;
            }
        });
    }
}
發佈了55 篇原創文章 · 獲贊 58 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章