android 绘制控件,Android_开发_Day29_自己绘制控件

Android_开发Day29自己绘制控件

目的:

在Android中很多时候系统的控件是不能满足需要的,组合方式定义控件又非常繁琐,因此此时需要自己画一个控件,才能满足需要

技术:

<1> 绘制控件时的步骤:

1.创建一个类并找一个类来继承

2.重写里面的三个构造方法

3.在onDraw(Canvas canvas)方法里绘制你的控件

<2> onDraw:

onDraw方法是系统调用的方法,在界面被创建之前改方法被系统调用,然后在里面用户可以自定义要画的东西,然后再由系统统一渲染到屏幕上,方法里面的一个参数canvas是系统提供的画布,在该画布上画控件,什么时候需要画控件,就是在系统的控件没法满足你要的控件的形状,特征和功能时就需要自己画控件,你画好的控件将由GPU渲染到屏幕上显示出来。缺点:如果频繁绘制那么占用内存可能会吃紧,因此能够使用系统的就不要自己画,原则。系统提供了一些基本的形状,下面看一看canvas里的一些基本方法:

方法

用法

drawLine()

参数是其实点坐标和终止点坐标最后加上一支画笔

drawCircle()

参数是圆点的坐标和半径和一支画笔

drawText()

参数是一个文本加要显示文本的x于y和一支画笔

drawBitmap()

参数是一张bitmap和一只画笔

drawColor()

参数就是一个颜色

drawOval()

画一个椭圆,参数是左右上下的距离和一支画笔

drawArc()

画一个扇形,参数是上下左右起始角度和终止角度和是否使用中心,最后还有一支画笔

<3> onMeasure方法:

加载控件时系统会调用此方法,其中的参数widthMeasureSpec是代表父控件预测的该控件的最大宽度,相对应的heightMeasureSpec是代表父控件预测的该控件的最大高度,一般在自己需要自定义控件的大小时可以重写该方法,如果不需要那父视图说你这个控件是多大那就是多大,调用关系如图:

9a15cddbea8c

image.png

<4> onSizeChanged方法:

控件测量完毕,或者控件尺寸改变了都会调用此方法,该方法一般作为一个过度,许多要控件定型后才能使用的代码可以在这里写。没 什么好讲的。

<5> 改变自定义控件的某个属性并及时展现出来的方法:

方法有多种,这里展示三种:

第一种:简单粗暴,点一下改变一下刷新一下

方法:1.讲要改变的属性弄成一个成员变量,并设置set,get方法

2.在onTouchEvent方法里面去set该属性

3.调用invalidate()方法,刷新并重绘控件

第二种:用一个子线程来每隔一定的时间改变一次,推荐使用Timer

方法:1.讲要改变的属性弄成一个成员变量,并设置set,get方法

2.new 一个Timer,调用schedule方法,参数传一个TimerTask接口对象,实现里面的run方法,run方法的内容就是set该属

性,第二个参数是多少秒后执行该run方法,传0,第三个参数是要隔多少秒后再次执行该方法,这个可以根据实际需要来

定。

3.调用invalidate()方法,刷新并重绘控件

缺点:上面的方法很多情况下会出错,因为我们在子线程里面去改变UI线程(主线程)的东西,系统很多时候不认账的,小弟怎么能动大哥的动西呢,但也不排除有些时候是可以的,具体是什么时候不清楚,可能和你写的代码的实际情况有关,可以自行百度,当然如果系统没报错,那你就尽情的用吧。若要改进可以用一个Handler,每当你改变完后通知主线程刷新就行了,小弟动不得大哥的东西,我通知你,让你自己动总行了吧。Handler用法可以参考以下代码:

//用handler来接收消息

final Handler handler = new Handler(){

public void handleMessage(Message msg) {

headPicture.invalidate();

}

};

//显示剩余时间

new Timer().schedule(new TimerTask() {

@Override

public void run() {

headPicture.setAchieve(getDATE(startDate));

handler.sendEmptyMessage(1);

}

},0, 1000);

第三种:还记得前面说Android中的动画时讲了几种动画,其中有一个ValueAnimator没用到么,因为它只能得到动画过程中的每一个中间值,却不会去做动画,说到这里是不是就发现什么了,我只要得到动画过程中的每一个值,然后手动set一下那个值,然后刷新一下不就行了吗,没错就是这样就可以做自定义控件的动画了。

方法:1.创建一个ValueAnimator的对象,然后ValueAnimator.ofInt/ValueAnimator.ofFloat你要的值是什么类型的就of什么,然后参

数传开始时的值和结束时的值就行,当然你要传中间值也没问题,从左往右依次传就行了。

2.添加监听器,调用addUpdateListener(),参数new一个ValueAnimator.AnimatorUpdateListener()接口,实现里面的

onAnimationUpdate方法,该方法会传一个ValueAnimator的对象过来,传过来时调用里面的getAnimatedValue()就得到了

这一时刻对应得值,赋值给要改变得属性即可,当然别忘刷新哦

下面是演示代码:

ValueAnimator va = ValueAnimator.ofInt(0, 360);

va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator valueAnimator) {

//改变属性值

outer = valueAnimator.getAnimatedValue()

//刷新

invalidate();

}

});

va.start();

<6> 画笔Paint的设置:

画笔Paint的设置来看一下里面的一些常用的方法:

方法

描述

setStrokeWidth

设置画笔的粗细,参数是一个整型变量

setColor

设置画笔的颜色,参数就是颜色值

setTextSize

设置文字的大小,在画文字的时候生效

setStyle

设置画笔的样式,是实心还是空心,参数是:STROKE(空心),FILL(实心)

setAntiAlias

设置画笔是否抗锯齿,true还是false

setAlpha

设置alpha值,也就是透明度

setStrokeCap

设置画笔的落笔的笔锋

<7> 文字的画法以及位置的确定:

画文字可以使用canvas里面的方法drawText(),该方法的参数是文字的文本以及文字的x和y,和一只画笔,其中画笔决定了文字大小,x和y决定了文字的位置,文本决定文字内容,但是系统是怎么确定文字的位置的,就是用到了文字的基准线,如图:

9a15cddbea8c

8[00_00_03][20191130-095441].png

图中的基准线是紧贴文字底部的那条线,有点像英语书写时的基准线,所有字母都写在该线的上面,这样就能很好的对齐。

同时还有一些距离如上图中的,Ascent就是文字的整个高度,当然如果要得到文字的宽度就可以调用paint里面的measureText()方法来计算文字的宽度,返回值即宽度,参数就是你的文字字符串,如果要得到Ascent需要先创建一个FontMetricsInt对象,才能得到,因此计算文字的坐标时算的其实是基准线的坐标。

<8> 贝塞尔曲线的使用:

要画贝塞尔曲线用canvas已经不行了,因为canvas只提供了一些基础图形的方法,复杂的图案还需要用到Path,所谓的Path也就是一个路径,其实你画的任何图形都是一个路径,只要我们跟着这个路径画就能画出图形,Path的基本方法:

方法

用法

moveTo

参数是x,y就是你落笔的点在哪里,默认0,0

lineTo

连线的终点,参数也是x,y

cubicTo

画贝塞尔曲线,参数是三个点,起点终点和拉伸点

quadTo

也是画贝塞尔曲线,参数相比上一个少一个点,也就是起点,可以通过moveTo方法来设定

rXXXXTo

也就是上面的方法名前加一个r就代表坐标的计算方式换成了相对坐标,相对于上一次的坐标的相对坐标

如何将Path画出来,可以通过canvas里面的drawPath()方法来画出路径,参数是Path和Paint

<9> ViewGroup:

ViewGroup是一个抽象类,继承于它时必须要实现里面的方法onLayout(),该方法是布局的时候必要的方法,系统要得到ViewGroup的大小时会先去测量子控件的大小会调用onMeasure()方法,然后再调用onLayout()弄清楚子控件的布局。onLayout方法有四个参数,从左往右依次是:left,top,right,bottom,见名知其意就是上下左右的距离。

技术如何使用:

做一个水波流动效果的进图条,水波流动效果可以通过贝塞尔曲线来做,进度值的改变可以用上面的三种方法中的一种来做,文字的提示百分比需要自己手动画上去,然后外边用一个⚪来将整个控件包起来,形成一个整体。参考代码如下:

类⭕和文字:

package com.example.waveloadingbyself;

import android.content.Context;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.util.AttributeSet;

import android.view.View;

public class CircleView extends View {

Paint circlePaint;

Paint textPaint;

//-----------------------------------------------------------------//

private int circleColor = Color.BLACK;

private int circleWidth = 10;

private int textColor = Color.BLACK;

private int textSize = 50;

private int centerYSpace;

//-----------------------------------------------------------------//

private double progress;

public CircleView(Context context) {

super(context);

init();

}

public CircleView(Context context, AttributeSet attrs) {

super(context, attrs);

init();

}

private void init(){

circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

circlePaint.setColor(circleColor);

circlePaint.setStrokeWidth(circleWidth);

circlePaint.setStyle(Paint.Style.STROKE);

//-----------------------------------------------------------------//

textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

textPaint.setColor(textColor);

textPaint.setTextSize(textSize);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

//确定半径

int radius = Math.min(getWidth(), getHeight()/2 - circleWidth);

//画⚪

canvas.drawCircle(getPivotX(), getPivotY(), radius, circlePaint);

//-----------------------------------------------------------------//

//计算文本宽度

System.out.println("CircleProgress:--"+progress);

String text = (int) (progress*100)+"%";

System.out.println("text:--"+text);

int width = (int) textPaint.measureText(text);

//获取文字的fontMetrics

Paint.FontMetricsInt fm = textPaint.getFontMetricsInt();

//画文字

canvas.drawText(text, getPivotX()-width/2, getPivotY()+(-fm.ascent)/2+centerYSpace, textPaint);

}

public void setCircleColor(int circleColor) {

this.circleColor = circleColor;

circlePaint.setColor(circleColor);

}

public void setCircleWidth(int circleWidth) {

this.circleWidth = circleWidth;

circlePaint.setStrokeWidth(circleWidth);

}

public void setTextColor(int textColor) {

this.textColor = textColor;

textPaint.setColor(textColor);

}

public void setTextSize(int textSize) {

this.textSize = textSize;

textPaint.setTextSize(textSize);

}

public double getProgress() {

return progress;

}

public void setProgress(double progress) {

this.progress = progress;

//System.out.println(progress);

invalidate();

}

public void setCenterYSpace(int centerYSpace) {

this.centerYSpace = centerYSpace;

}

}

类波浪:

package com.example.waveloadingbyself;

import android.animation.ValueAnimator;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.graphics.Path;

import android.util.AttributeSet;

import android.view.View;

import android.view.animation.LinearInterpolator;

public class WaveView extends View {

private Paint paint;

private Path path;

private ValueAnimator va;

float density = getResources().getDisplayMetrics().density;

private int waveLength = (int) (100*density);

private int waveCrest = (int) (50*density);

private int speed;

private int lineColor = Color.BLACK;//线条颜色

private int lineSize = 10;//线条粗细

private int centerYSpace;

public WaveView(Context context) {

super(context);

init();

}

public WaveView(Context context, AttributeSet attrs) {

super(context, attrs);

init();

}

private void init(){

paint = new Paint(Paint.ANTI_ALIAS_FLAG);

paint.setColor(lineColor);

paint.setStrokeWidth(lineSize);

paint.setStyle(Paint.Style.STROKE);

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

startWave();//开始动画

}

public void startWave(){

va = ValueAnimator.ofInt(0, waveLength);

va.setDuration(500);

va.setRepeatCount(ValueAnimator.INFINITE);

va.setRepeatMode(ValueAnimator.RESTART);

va.setInterpolator(new LinearInterpolator());

va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator valueAnimator) {

speed = (int) valueAnimator.getAnimatedValue();

invalidate();//刷新

}

});

va.start();

}

public void stopWave(){

if (va != null) {

va.cancel();

}

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

path = new Path();

//计算周期,完整的波

int count = getWidth() / waveLength;

path.moveTo(-waveLength, getHeight() / 2);

//获取中心线

int centerY = (int) getPivotY();

//确定曲线的路径

for (int start = -waveLength+speed; start < getWidth(); start += waveLength) {

path.cubicTo(start, centerY+centerYSpace, start+waveLength/4, centerY-waveCrest+centerYSpace, start+waveLength/2, centerY+centerYSpace);

path.cubicTo(start+waveLength/2, centerY+centerYSpace, start+waveLength*3/4, centerY+waveCrest+centerYSpace, start+waveLength, centerY+centerYSpace);

}

canvas.drawPath(path, paint);

}

/**

* 父容器会按照自己的规则给出一个方案

* 子View通过MeasureSpec.getMode .getSize获取相应的值

* getMode:

* Unspecified 无限制的 父容器没有对控件进行约束 肌肤不可能

* At_Most 不能超过最大值 一般就是包裹内容

* Exactly 确定的值 (200dp)

* @param widthMeasureSpec

* @param heightMeasureSpec

*/

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int mode = MeasureSpec.getMode(widthMeasureSpec);

int width = MeasureSpec.getSize(widthMeasureSpec);

switch (mode){

case MeasureSpec.UNSPECIFIED:

System.out.println("UNSPECIFIED:"+width);

break;

case MeasureSpec.AT_MOST:

System.out.println("AT_MOST:"+width);

break;

default:

System.out.println("EXACTLY:"+width);

break;

}

}

public void setWaveLength(int waveLength) {

this.waveLength = waveLength;

}

public void setWaveCrest(int waveCrest) {

this.waveCrest = waveCrest;

}

public void setLineColor(int lineColor) {

this.lineColor = lineColor;

paint.setColor(lineColor);

}

public void setLineSize(int lineSize) {

this.lineSize = lineSize;

paint.setStrokeWidth(lineSize);

}

public void setCenterYSpace(int centerYSpace) {

this.centerYSpace = centerYSpace;

}

}

组合类上述控件类:

package com.example.waveloadingbyself;

import android.content.Context;

import android.graphics.Color;

import android.util.AttributeSet;

import android.view.ViewGroup;

public class WaveLodging extends ViewGroup {

private double process;

public WaveLodging(Context context) {

super(context);

}

public WaveLodging(Context context, AttributeSet attrs) {

super(context, attrs);

}

@Override

protected void onLayout(boolean b, int i, int i1, int i2, int i3) {

WaveView waveView = new WaveView(getContext());

waveView.layout(0, 0, getWidth(), getHeight());

waveView.setLineSize(20);

waveView.setLineColor(Color.BLUE);

waveView.setCenterYSpace((int) (getHeight() / 2 - process *getHeight()));

addView(waveView);

CircleView circleView = new CircleView(getContext());

circleView.setCircleColor(Color.RED);

circleView.setCircleWidth(30);

circleView.layout(0, 0, getWidth(), getHeight());

circleView.setProgress(process);

addView(circleView);

CircleView circleView1 = new CircleView(getContext());

circleView1.setCircleWidth(180);

circleView1.setCircleColor(0xFFF8F8F9);

circleView1.setProgress(process);

circleView1.layout(-250, -250, getWidth()+250, getHeight()+250);

addView(circleView1);

}

public void setProcess(double process) {

this.process = process;

}

}

MainActivity代码:

package com.example.waveloadingbyself;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

WaveLodging waveLodging = findViewById(R.id.lView);

waveLodging.setProcess(0.33);

}

}

xml里面的代码:

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context=".MainActivity">

android:id="@+id/lView"

android:layout_width="300dp"

android:layout_height="300dp"

android:layout_centerInParent="true" />

实际使用效果:

9a15cddbea8c

运行结果图.png

总结:

自定义控件的方式,最好的肯定是自己画的,但效率最高的还是系统的,因此当系统控件能用时一定要用系统的,不能用时才要自己定义,因此那些自己定义的好的控件可以封装起来保存好了,以便以后要用的时候随时都能拿出来用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值