您的位置:首页 > 移动开发 > Android开发

自定义View基础与原理

2016-02-29 23:06 453 查看
这里写链接内容# 自定义View基础与原理

什么是自定义View

其实就是继承系统的View,然后加入绘制元素(文字/图形)和逻辑,最终达到自己想要想过的控件。

为什么使用自定义View

特定的显示风格

处理特有的用户交互

优化布局

封装等

如何自定义控件

编写自己的自定义View

- 编写最简单的自定义View,什么都不显示,但是有View的特性


/**
* java代码创建视图的时候被调用,如果是从xml填充的视图,就不会调用这个
*
* @param context
*/
public MyView(Context context) {
super(context);
}

/**
* 这个是在xml创建但是没有指定style的时候被调用
*
* @param context
* @param attrs
*/
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}

/**
* 这个是在xml创建且指定style的时候被调用
*
* @param context
* @param attrs
* @param defStyleAttr
*/
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}


那种情况下调用那个构造方法,上面注释已经写的很清楚了。不妨我们想一下,让第一个构造方法去调用第二个构造方法,第二个去调用第三个,这样会不会省去一些事呢。如下面例子的代码所示。

- 可以显示自己的元素(文字/几何图形/图片)


public class MyView extends View {

private Paint paint;
private Bitmap bitmap;

/**
* java代码创建视图的时候被调用,如果是从xml填充的视图,就不会调用这个
*
* @param context
*/
public MyView(Context context) {
this(context, null);
}

/**
* 这个是在xml创建但是没有指定style的时候被调用
*
* @param context
* @param attrs
*/
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

/**
* 这个是在xml创建且指定style的时候被调用
*
* @param context
* @param attrs
* @param defStyleAttr
*/
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

init();
}

private void init() {
// Paint样式和颜色信息,关于文字/几何图形和bitmap的
paint = new Paint();

bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setTextSize(30);
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.STROKE);
// 绘制文字
canvas.drawText("this is onDraw", 0, 30, paint);

// 绘制直线
canvas.drawLine(0, 60, 100, 60, paint);

// 绘制矩形
canvas.drawRect(0, 90, 100, 190, paint);
// Rect中参数int类型
Rect r = new Rect(100, 90, 200, 190);
canvas.drawRect(r, paint);
// Rectf中参数float类型
RectF rect = new RectF(200, 90, 300, 190);
canvas.drawRect(rect, paint);
// 圆角矩形rx, ry x和y方向上的弧度
RectF rect2 = new RectF(300, 90, 400, 190);
canvas.drawRoundRect(rect2, 30, 30, paint);

// 绘制圆形
canvas.drawCircle(50, 270, 50, paint);

// 绘制图片
canvas.drawBitmap(bitmap, 0, 350, paint);
}

}


布局中使用

<!-- 完整的包名和类名 -->
<com.example.myview.MyView
android:layout_width="match_parent"
android:layout_height="match_parent"/>


运行结果如图:



加入逻辑线程

怎么让元素动起来呢,需要什么

不断的改变元素绘制的坐标位置,我们在视觉上就会感觉到元素在运动

元素动起来的逻辑放在哪里

创建一个线程,执行run方法里面改变其坐标,在进行重新绘制,android提供了在线程中重新绘制的方法postInvalidate()

怎样看起来动起来的元素流畅

一秒内绘制20次即可

public class LogicView extends View {
private Paint paint;
private float rx;
/**
* 逻辑线程
*/
private MyThread mThread;
private RectF rectF;
/**
* 区间角度
*/
private float sweepAngle;

public LogicView(Context context) {
this(context, null);
}

public LogicView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public LogicView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

init();
}

/**
* 对类成员进行初始化
*/
private void init() {
paint = new Paint();
rectF = new RectF(0, 60, 100, 160);
mThread = new MyThread();
mThread.start();
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setTextSize(30);
canvas.drawText("LogicView", rx, 30, paint);

// 起始角度/区间角度
canvas.drawArc(rectF, 0, sweepAngle, true, paint);
}

class MyThread extends Thread {
Random random = new Random();

@Override
public void run() {
super.run();
// 不断的进行绘制
while (true) {
long start = System.currentTimeMillis();
// 超出屏幕长度
if ((rx += 3) > getWidth()) {
rx = 0 - paint.measureText("LogicView");
}
// 超出360度,清零
if (sweepAngle++ > 360) {
sweepAngle = 0;
}

int r = random.nextInt(256);
int g = random.nextInt(256);
int b = random.nextInt(256);
paint.setARGB(255, r, g, b);

// 线程中更新绘制的方法
// postInvalidate();
invalidateView();

long end = System.currentTimeMillis();
// 一秒绘制20次即可
if (end - start < 50) {
try {
Thread.sleep(50 - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

/**
* 重绘
*/
private void invalidateView() {
if (Looper.getMainLooper() == Looper.myLooper()) {
invalidate();
} else {
postInvalidate();
}
}
}


提取和封装自定义View

封装后的基类

public abstract class BaseView extends View {
/**
* 逻辑线程
*/
private MyThread mThread;

/**
* 线程的控制开关
*/
private boolean isRunning = true;

public BaseView(Context context) {
this(context, null);
}

public BaseView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public BaseView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

// 这里使用final关键字,不让子类重写
@Override
protected final void onDraw(Canvas canvas) {
if (mThread == null) {
mThread = new MyThread();
mThread.start();
} else {
drawSub(canvas);
}
}

/**
* 绘制元素
*
* @param canvas
*/
protected abstract void drawSub(Canvas canvas);

/**
* 逻辑处理
*/
protected abstract void logic();

//离开屏幕的时候结束掉线程
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();

isRunning = false;
}

class MyThread extends Thread {
@Override
public void run() {
super.run();
// 不断的进行绘制
while (isRunning) {
long start = System.currentTimeMillis();
logic();

invalidateView();

long end = System.currentTimeMillis();
// 一秒绘制20次即可
if (end - start < 50) {
try {
Thread.sleep(50 - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

/**
* 重绘
*/
public void invalidateView() {
if (Looper.getMainLooper() == Looper.myLooper()) {
invalidate();
} else {
postInvalidate();
}
}
}


该过程中我们提出的绘制元素drawSub(Canvas canvas)方法和逻辑处理logic()方法。并将onDraw使用final关键字,不让子类去重写。当绘制离开屏幕的时候,结束掉逻辑处理线程。

使用基类

public class LogicView extends BaseView {

private Paint paint;
private float rx;
private RectF rectF;
/**
* 区间角度
*/
private float sweepAngle;

private Random random;

public LogicView(Context context) {
this(context, null);
}

public LogicView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public LogicView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

init();
}

/**
* 对类成员进行初始化
*/
protected void init() {
paint = new Paint();
rectF = new RectF(0, 60, 100, 160);

random = new Random();
}

@Override
protected void drawSub(Canvas canvas) {
paint.setTextSize(30);
canvas.drawText("LogicView", rx, 30, paint);

// 起始角度/区间角度
canvas.drawArc(rectF, 0, sweepAngle, true, paint);

}

@Override
protected void logic() {
// 超出屏幕长度
if ((rx += 3) > getWidth()) {
rx = 0 - paint.measureText("LogicView");
}
// 超出360度,清零
if (sweepAngle++ > 360) {
sweepAngle = 0;
}

int r = random.nextInt(256);
int g = random.nextInt(256);
int b = random.nextInt(256);
paint.setARGB(255, r, g, b);
}
}


运行结果如图:



定义XML中定义的样式来影响显示效果

自定义样式attr.xml

新建自定义样式attr.xml在res/values目录下

<?xml version="1.0" encoding="utf-8"?>
<resources>

<declare-styleable name="NumText">

<!-- 绘制的行数 -->
<attr name="lineNum" format="integer"></attr>
<!-- 文字是否滚动显示 -->

<attr name="isScrool" format="boolean"></attr>
</declare-styleable>

</resources>


在布局中使用

在使用自定义属性的时候先声明xml的命名空间

eclipse:

xmlns:myview=”http://schemas.android.com/apk/res/完整的包名”

android studio:

xmlns:myview=”http://schemas.android.com/apk/res-auto”

布局中的使用:

<com.example.myview.v4.NumText
android:layout_width="match_parent"
android:layout_height="match_parent"
myview:isScrool="true"
myview:lineNum="3" >
</com.example.myview.v4.NumText>


例子代码

public class NumText extends BaseView {
/**
* 画笔
*/
private Paint paint;
/**
* 绘制文字x坐标
*/
private float rx;
/**
* 绘制的行数
*/
private int lintNum;
private Random random;
/**
* 是否滚动
*/
private boolean isScrool;

public NumText(Context context) {
this(context, null);
}

public NumText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public NumText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

paint = new Paint();
random = new Random();
//默认不滚动
isScrool = false;
//在view构造方法中获取自定义属性
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.NumText);

int n = a.getIndexCount();

for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.NumText_lineNum:
lintNum = (int) a.getInt(attr, 1);
break;

case R.styleable.NumText_isScrool:
isScrool = (boolean) a.getBoolean(attr, false);
break;
}

}

a.recycle();
}

@Override
protected void drawSub(Canvas canvas) {
// 绘制lineNum行文字
for (int i = 0; i < lintNum; i++) {
int textSize = 30 + i;
paint.setTextSize(textSize);
canvas.drawText("自定义View基础与原理", rx, textSize + textSize * i, paint);
}
}

@Override
protected void logic() {
if(isScrool){
// 超出屏幕长度
if ((rx += 5) > getWidth()) {
rx = 0 - paint.measureText("自定义View基础与原理");
}
// 改变画笔的颜色
int r = random.nextInt(256);
int g = random.nextInt(256);
int b = random.nextInt(256);
paint.setARGB(255, r, g, b);
}
}
}


运行结果:


源码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息