自定義控件那些事兒 ------ 六【繪製路徑】

一、繪製線

Android中提供了很多繪製的方法,主要在Canvas中展示。兩點連接就成爲線,多點順序連接就成爲路徑。爲學習本階段內容,先實現基礎的Demo。

1,自定義繪製路徑控件


/**
 * 繪製路徑自定義控件
 */

public class LineView extends View {
    /**
     * 繪製路徑
     */
    private Path path;
    /**
     * 繪製筆
     */
    private Paint paint;
    /**
     * 上一個點位的座標值
     */
    private float preX, preY;


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

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

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

    /**
     * 初始化
     */
    private void init() {
        path = new Path();

        paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.CYAN);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(10);
}


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                path.moveTo(event.getX(), event.getY());
                return true;//消耗掉事件
//                break;
            case MotionEvent.ACTION_MOVE:
                path.lineTo(event.getX(), event.getY());
                invalidate();
//                postInvalidate();
                break;

            default:
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(path, paint);
    }


    /**
     * 將路徑重置【清空頁面數據】
     */
    public void reset() {
        path.reset();
        invalidate();
    }
}

2,自定義控件的使用


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="com.future.rectdrawdemo.MainActivity">

    <Button
        android:id="@+id/clear_bt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="清空"
        />

    <com.future.rectdrawdemo.view.LineView
        android:id="@+id/line_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/clear_bt" />

</RelativeLayout>

3,實際中的使用


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    /**
     * 清空
     */
    private Button clearBT;
    /**
     * 繪製內容
     */
    private LineView lineView;


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

        initView();
        initData();
        initListener();
    }


    /**
     * 初始化控件
     */
    private void initView() {
        clearBT = findViewById(R.id.clear_bt);
        lineView = findViewById(R.id.line_view);
    }


    /**
     * 初始化數據
     */
    private void initData() {

    }

    /**
     * 初始化監聽器
     */
    private void initListener() {
        clearBT.setOnClickListener(this);

    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.clear_bt:
                lineView.reset();
                break;
        }
    }
}

展示效果:




備註:自定義控件中的invalidate(),postinvalidate()都是用於刷新頁面的。postinvalidate()用於子線程中刷新頁面,在onTouchEvent()中使用結果是一致的。


二、繪製貝塞爾曲線


1,貝塞爾曲線原理理解


在實際的使用中,拐角的處理是需要比較順滑的。繪製直線的需求遠不夠用,爲解決順滑的轉角,引入貝塞爾曲線。

貝塞爾曲線原理講解十分清楚,值得仔細看看。


基於以上原理的講解,我們理解二階貝塞爾曲線暴力一些,除了開始、結束兩點,添加控制點就能夠繪製出這兩點直接的二階貝塞爾曲線。在Android中實現了二階三階貝塞爾曲線。二階貝塞爾曲線更常用,做詳細的解讀。

二階貝塞爾曲線的實現需要三個點位,至於內部的具體實現,Android內部已經實現,可以直接調用。當然,若是想要更好的理解,可以自己實現封裝的方法來加深記憶。

貝塞爾曲線繪製工具這裏有一個很好的工具應用,推薦大家嘗試,以便於理解深刻。以下展示幾種情形下的貝塞爾曲線:

(1)二階貝塞爾曲線特例 -- 三個點位中,有兩點重合


爲了演示效果,第一第二點位是有一點差別的,在代碼中實現時,是可以完全重合的。這是爲之後的奠定基礎的。

(2)正常的二階貝塞爾曲線


(3)高階貝塞爾曲線-5



2、二階貝塞爾曲線繪製的實現


/**
 * 貝塞爾曲線繪製類
 */

public class BerzPathView extends View {
    /**
     * 繪製筆
     */
    private Paint paint;

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

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


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


    /**
     * 初始化
     */
    private void init() {
        paint = new Paint();

    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.GREEN);
        paint.setTextSize(4);
        paint.setStrokeWidth(10);

        Path path = new Path();
        path.moveTo(100, 300);
/*        path.quadTo(200, 200, 300, 300);
        path.quadTo(400, 400, 500, 300);*/

//相對定位
        path.rQuadTo(100, -100, 200, 0);
        //(300,300)
        path.rQuadTo(100, 100, 200, 0);

        canvas.drawPath(path, paint);
    }
}

moveTo()移動到開始點位,quadTo()則是指定控制點位和結束點位。

實例中表示:開始點[100,300],控制點[200,200],結束點[300,300];

     第二次quadTo()時,以上一次的結束點爲開始點,開始點位[300,300],控制點[400,400],結束點[500,300]。同時這也是實現了物理意義上的一個波長路徑。


另一種實現方式是相對位置,rQuadTo();

rQuadTo(100,-100,200,0)相當於quadTo(開始點X +100,開始點Y+(-100),開始點X +200,開始點Y+ 0)。

實際上,以上兩種方式實現展現的結果是一致的。




三、波浪自定義控件


基於以上的內容,實現一個自定義控件,波浪運動,並能夠控制內部“水”的多少。

1,實現思路

在上面實現的控件中,將波浪重複多個,鋪滿整屏時,就有了波浪波峯的表示。讓波浪運動起來也就有了運動效果。

控制波浪的高度,實現讓“水”量的增多減少。


2,細分實現

(1)繪製多個波浪,爲了保證能夠運動,屏幕左右多添加一屏的寬度並構成封閉路徑;

(2)添加屬性動畫,讓波浪運動起來;

(3)使用時刷新波浪高度,實現“水”增多減少。


3,自定義控件實現


/**
 * 波浪控件  2017/12/5.
 */

public class WaveView extends View {
    /**
     * 繪製路徑
     */
    private Path path;
    /**
     * 繪製筆
     */
    private Paint paint;
    /**
     * 上一個點位的座標值
     */
    private float preX, preY;
    /**
     * 單波波長【這個是同一x軸上最近兩點之間的距離(與物理的波長是一般的關係)】
     */
    private int mItemWaveLength = 400;
    /**
     * 動畫刷新單位時間內平移距離
     */
    private int dx;


    /**
     * 波浪起點高度
     */
    private int originY;


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

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

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


    /**
     * 初始化
     */
    private void init() {
        path = new Path();

        paint = new Paint();
//        paint.setStyle(Paint.Style.STROKE);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setColor(Color.CYAN);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(10);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }

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

        int halfWaveLen = mItemWaveLength / 2;
        path.moveTo(-mItemWaveLength + dx, originY);
        //左右各多出一個波長
        for (int i = -mItemWaveLength; i <= getWidth() + mItemWaveLength; i += mItemWaveLength) {
            path.rQuadTo(halfWaveLen / 2, -50, halfWaveLen, 0);
            path.rQuadTo(halfWaveLen / 2, 50, halfWaveLen, 0);
        }

        //封閉圖形
        path.lineTo(getWidth(), getHeight());
        path.lineTo(0, getHeight());
        path.close();

        canvas.drawPath(path, paint);
    }


    public void startAnim() {
        ValueAnimator animator = ValueAnimator.ofInt(0, mItemWaveLength);//平移動畫,一個波長的距離
        animator.setDuration(2000);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                dx = (int) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.start();
    }

    /**
     * 設置起始高度
     *
     * @param originY
     */
    public void setOriginY(int originY) {
        this.originY = originY;
        invalidate();
    }
}

4,控件的使用


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="com.future.rectdrawdemo.MainActivity">

    <com.future.rectdrawdemo.view.WaveView
        android:id="@+id/wave_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />


</RelativeLayout>

5,控件的維護


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    /**
     * 波浪自定義控件
     */
    private WaveView waveView;

    /**
     * 消息處理器
     */
    private MainHandler handler;
    /**
     * 表示當前進度
     */
    private int progress;

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

        handler = new MainHandler(MainActivity.this);

        initView();
        initData();
        initListener();
    }


    /**
     * 初始化控件
     */
    private void initView() {
        waveView = findViewById(R.id.wave_view);
    }


    /**
     * 初始化數據
     */
    private void initData() {
        waveView.startAnim();
        handler.sendEmptyMessage(10001);
    }

    /**
     * 初始化監聽器
     */
    private void initListener() {

    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
           
        }
    }

    private void freshWaveView() {
        if (progress < 100) {
            waveView.setOriginY(progress * 10);
            progress += 10;
            handler.sendEmptyMessageDelayed(10001, 1000);
        }
    }

    static class MainHandler extends Handler {
        WeakReference<MainActivity> mWeak;

        public MainHandler(MainActivity activity) {
            mWeak = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity mActivity = mWeak.get();

            switch (msg.what) {
                case 10001:
                    mActivity.freshWaveView();
                    break;
            }
            super.handleMessage(msg);
        }
    }
}

展示效果:




源碼傳送門



Be the change you want to see in the world. (自己去做或者成爲你想看到的改變)


發佈了121 篇原創文章 · 獲贊 32 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章