二階貝塞爾曲線之波浪圖(二)

昨天我寫了二階貝塞爾曲線之波浪圖之後,結果裝逼失敗。理由是大佬說這個波浪圖的效果很生硬,一般情況下波浪圖是配合手勢使用的。因此決定改寫該控件,實現利用手勢實現波浪圖。先來一張效果圖:

這裏寫圖片描述

實現思路如下:

  1. 在RecyclerView的onTouch事件中,獲取當前頁面加載的最後一個可見item的下標Position,確認RecyclerView是否加載到底部?
  2. 上述確認加載到到底部後,處理onTouch事件(判斷是長按事件還是點擊事件)
  3. 在長按事件中將滑動過程中的X值傳給自定義控件,然後自定義控件根據x值繪製貝塞爾曲線並顯示控件;
  4. 在手勢擡起後調用自定義控件方法,重置參數並隱藏控件

一、我本來打算在RecyclerView的item佈局文件中自定義控件,但是發現獲取不到控件,後來想了一下,這樣會把最後一項Item佈局撐大,於是還是加在了根佈局上。
RecyclerView獲取最後一個可見Item有兩個方法,但是都需要實現同一個抽象類RecyclerView.OnScrollListener,然後我們可以使用一個boolean的標誌位來記錄RecyclerView是否加載到底部?代碼如下:

 mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                    //當滾動結束
                    if(newState == RecyclerView.SCROLL_STATE_IDLE){
                         //當前分頁加載的demo數據最多隻有41條,所以下標值是40
                        check =((LinearLayoutManager)mRecyclerView.getLayoutManager()).findLastVisibleItemPosition() == 40;
                    }
                }

                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);
                    //得到當前顯示的最後一個item的view
                    View lastChildView = recyclerView.getLayoutManager().getChildAt(recyclerView.getLayoutManager().getChildCount()-1);
                    //當前分頁加載的demo數據最多隻有41條,所以下標值是40
                    check  = recyclerView.getLayoutManager().getPosition(lastChildView) == 40;
                }
            });

二、在onTouch事件判斷當前事件爲觸摸還是還是點擊、長按等其它事件,其實這個方法一般就兩種:

  1. 通過觸摸的時間來判斷
  2. 通過滑動的距離來判斷

由於爲了不影響長按事件,這裏選擇通過滑動距離來判斷,代碼如下:

   @Override
    public boolean onTouch(View v, MotionEvent event) {

        if(check){
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    downX = event.getX();
                    downY = event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float moveX = event.getX();
                    float moveY = event.getY();
                    if (Math.abs(moveX - downX) > dp5 && Math.abs(moveY - downY) > dp5) {
                        waveView.setPoint(moveX);
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    float upX = event.getX();
                    float upY = event.getY();
                    //加載到頂部後的點擊或者長按事件
                    if (Math.abs(upX - downX) < dp5 && Math.abs(upY - downY) < dp5) {
                       return false;
                    }

                    break;
            }
            return true;
        }
        return false;
    }

三、在長按事件中將滑動過程中的X值傳給自定義控件,然後自定義控件根據x值繪製貝塞爾曲線並顯示控件。整個過程就是自定義控件有一個公共方法接收參數,並刷新視圖。雖然自定義控件的核心還是繪製貝塞爾曲線,但是一些參數有了變化,這裏做一個說明。

這裏寫圖片描述
這裏寫圖片描述
製圖技術實在不行,因此將就看吧,看不懂就看源碼:


/**
 * Created by 魏興 on 2017/6/12.
 */

public class WaveView extends View  {

    private static final String TAG = "WaveView";
    //波浪畫筆
    private Paint mPaint;


    //波浪Path類
    private Path mPath;

    private float controlpoint;


    //波紋的中間軸
    private int mCenterY;

    //屏幕高度
    private int mScreenHeight;
    //屏幕寬度
    private int mScreenWidth;

    public WaveView(Context context) {
        super(context);
        init();
    }

    public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public WaveView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mPath = new Path();
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.LTGRAY);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);



    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mScreenHeight = h;
        mScreenWidth = w;

        mCenterY = mScreenHeight * 3/ 5;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mPath.reset();

        int start;
        int end;
        boolean isLeft = true;
        if(controlpoint>=mScreenWidth/2){

            isLeft = false;
        }
        start = isLeft ? mCenterY/3:mCenterY;
        end =  isLeft ? mCenterY:mCenterY/3;

        mPath.moveTo(0, start);
        mPath.quadTo(controlpoint,mCenterY/3, mScreenWidth,end);

        //填充矩形
        mPath.lineTo(mScreenWidth, mScreenHeight);
        mPath.lineTo(0, mScreenHeight);
        mPath.close();
        canvas.drawPath(mPath, mPaint);
    }

    public void setPoint(float x ){

        this.controlpoint = x;
        setVisibility(VISIBLE);
        invalidate();
    }

    public void rest(){
        this.controlpoint = 0;
        setVisibility(GONE);
    }

}

感覺手勢擡起以後貝塞爾曲線的圖形直接隱藏還是很生硬,加一個動畫:

     @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mPath.reset();

        int startY;
        int endY;
        boolean isLeft = true;
        if(controlpoint>=mScreenWidth/2){

            isLeft = false;
        }

        startY = isLeft ? mCenterY/3:mCenterY;
        endY =  isLeft ? mCenterY:mCenterY/3;

        mPath.moveTo(0, startY+offset);
        mPath.quadTo(controlpoint,0+offset, mScreenWidth,endY+offset);
        Log.e(TAG, "onDraw: "+(startY)+"   " +0+"   "+endY);
        //填充矩形
        mPath.lineTo(mScreenWidth, mScreenHeight);
        mPath.lineTo(0, mScreenHeight);
        mPath.close();
        canvas.drawPath(mPath, mPaint);
    }

    public void setPoint(float x ){
        if(!isRestting){
            this.controlpoint = x;
            setVisibility(VISIBLE);
            invalidate();
        }

    }
    private boolean isRestting = false;
    public void rest(){
//        setVisibility(GONE);
//        controlpoint = 0;
        isRestting = true;
        ValueAnimator animator = ValueAnimator.ofInt(0, mScreenHeight);
        animator.setDuration(400);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                offset = (int) animation.getAnimatedValue();
                postInvalidate();
            }
        });

        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                setVisibility(GONE);
                controlpoint = 0;
                mScreenHeight = height;
                mCenterY = mScreenHeight * 3/ 5;
                offset = 0;
                isRestting = false;
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        animator.start();
    }

再看看效果:

這裏寫圖片描述

源碼

參考資料:

Recyclerview中最後一個可見Item的位置以及是否滑動到底部

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章