自定义控件那些事儿 ------ 六【绘制路径】

一、绘制线

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. (自己去做或者成为你想看到的改变)


猜你喜欢

转载自blog.csdn.net/u013205623/article/details/78719740