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