一、繪製線
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. (自己去做或者成爲你想看到的改變)