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