在一些常用的View,无法满足我们小小的心愿时,我们考虑自定义一个View
自定义View 流程 及其简单 只要知道 View 的绘制流程 就ok ,重点 部分在于 如何实现各自的需求,如何编写自己任务的相应算法 及数据结构的组织。
下面是一个对颜色控制的小例子,apidemons-colorMatrix;
铺垫0:
引用:《Android内核剖析》第13章----View工作原理总结而成的
感谢:http://blog.csdn.net/qinjuning/article/details/7110211
整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为
根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘
(draw),其框架过程如下:
铺垫1:
接下来温习一下整个View树的结构,对每个具体View对象的操作,其实就是个递归的实现。
正题:
public class MainActivity extends GraphicsActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private static final String TAG = "SampleView";
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/**
* 颜色矩阵 {r,g,b,a}
* */
private ColorMatrix mCM = new ColorMatrix();
private Bitmap mBitmap;
private float mSaturation;
private float mAngle;
public SampleView(Context context) {
super(context);
mBitmap = BitmapFactory.decodeResource(context.getResources(),
R.drawable.balloons);
}
private static void setTranslate(ColorMatrix cm, float dr, float dg,
float db, float da) {
cm.set(new float[] {
2, 0, 0, 0, dr,
0, 2, 0, 0, dg,
0, 0, 2, 0, db,
0, 0, 0, 1, da });
}
private static void setContrast(ColorMatrix cm, float contrast) {
float scale = contrast + 1.f;
float translate = (-.5f * scale + .5f) * 255.f;
cm.set(new float[] {
scale, 0, 0, 0, translate,
0, scale, 0, 0, translate,
0, 0, scale, 0, translate,
0, 0, 0, 1, 0 });
}
private static void setContrastTranslateOnly(ColorMatrix cm, float contrast) {
float scale = contrast + 1.f;
float translate = (-.5f * scale + .5f) * 255.f;
cm.set(new float[] {
1, 0, 0, 0, translate,
0, 1, 0, 0, translate,
0, 0, 1, 0, translate,
0, 0, 0, 1, 0 });
}
private static void setContrastScaleOnly(ColorMatrix cm, float contrast) {
float scale = contrast + 1.f;
float translate = (-.5f * scale + .5f) * 255.f;
cm.set(new float[] {
scale, 0, 0, 0, 0,
0, scale, 0, 0, 0,
0, 0, scale, 0, 0,
0, 0, 0, 1, 0 });
}
@Override protected void onDraw(Canvas canvas) {
Log.i(TAG,"onDraw");
Paint paint = mPaint;
float x = 100;
float y = 140;
canvas.drawColor(Color.WHITE);
paint.setColorFilter(null);
canvas.drawBitmap(mBitmap, x, y, paint);
ColorMatrix cm = new ColorMatrix();
mAngle += 2;
if (mAngle > 180) {
mAngle = 0;
}
//convert our animated angle [-180...180] to a contrast value of [-1..1]
float contrast = mAngle / 180.f;
setContrast(cm, contrast);
paint.setColorFilter(new ColorMatrixColorFilter(cm));
canvas.drawBitmap(mBitmap, x + mBitmap.getWidth() + 10, y, paint);
setContrastScaleOnly(cm, contrast);
paint.setColorFilter(new ColorMatrixColorFilter(cm));
canvas.drawBitmap(mBitmap, x, y + mBitmap.getHeight() + 10, paint);
setContrastTranslateOnly(cm, contrast);
paint.setColorFilter(new ColorMatrixColorFilter(cm));
canvas.drawBitmap(mBitmap, x, y + 2*(mBitmap.getHeight() + 10),
paint);
invalidate();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
// TODO Auto-generated method stub
Log.i(TAG,"onlayout");
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
Log.i(TAG,"onMeasure");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
package com.example.mycolomatrix;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewGroup.LayoutParams;
/**
* onMeasure 属于View的方法,用来测量自己及其子元素来确定宽度和高度
* onMeasure中必须确定 measured width and height of this view,
*
*onlayout 属于ViewGroup的方法:用来为当前ViewGroup的子元素的位置和大小(大小是layout分配给本元素的显示空间大小,与本元素的大小可能不一样)
*
* onMeasure在onLayout之前调用
* 设置background后,会重新调用onMeasure和onLayout
* */
public class PictureLayout extends ViewGroup {
private String TAG="PictureLayout";
private final Picture mPicture = new Picture();
public PictureLayout(Context context) {
super(context);
Log.i(TAG, "PictureLayout");
}
public PictureLayout(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i(TAG, "PictureLayout2");
}
@Override
public void addView(View child) {
Log.i(TAG, "addView1");
if (getChildCount() > 1) {
throw new IllegalStateException("PictureLayout can host only one direct child");
}
super.addView(child);
}
@Override
public void addView(View child, int index) {
Log.i(TAG, "addView2");
if (getChildCount() > 1) {
throw new IllegalStateException("PictureLayout can host only one direct child");
}
super.addView(child, index);
}
@Override
public void addView(View child, LayoutParams params) {
Log.i(TAG, "addView2p");
if (getChildCount() > 1) {
throw new IllegalStateException("PictureLayout can host only one direct child");
}
super.addView(child, params);
}
@Override
public void addView(View child, int index, LayoutParams params) {
Log.i(TAG, "addView3p");
if (getChildCount() > 1) {
throw new IllegalStateException("PictureLayout can host only one direct child");
}
super.addView(child, index, params);
}
/**
* a set of default layout parameters or null
* 设置一个默认的 布局属性
* 返回一组宽度为WRAP_CONTENT,高度为WRAP_CONTENT,坐标是(0,0)的布局参数
* */
@Override
protected LayoutParams generateDefaultLayoutParams() {
Log.i(TAG, "generateDefaultLayoutParams");
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
/**
*测量视图以确定其内容宽度和高度。此方法被measure(int, int)调用。需要被子类重写以提供对其内容准确高效的测量。
*约定:当重写此方法时,你必须调用setMeasuredDimension(int, int)来保存当前视图view的宽度和高度。不成功调用此方法将会导致一个IllegalStateException异常,
*是由measure(int, int)抛出。所以调用父类的onMeasure(int, int)方法是必须的。
*父类的实现是以背景大小为默认大小,除非MeasureSpec(测量细则)允许更大的背景。子类可以重写onMeasure(int,int)以对其内容提供更佳的尺寸。
*如果此方法被重写,那么子类的责任是确认测量高度和测量宽度要大于视图view的最小宽度和最小高度(getSuggestedMinimumHeight() and getSuggestedMinimumWidth()),
*使用这两个方法可以取得最小宽度和最小高度。
*参数
widthMeasureSpec 强加于父节点的横向空间要求。要求是使用View.MeasureSpec进行编码。
heightMeasureSpec 强加于父节点的纵向空间要求。要求是使用View.MeasureSpec进行编码。
* */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i(TAG, "onMeasure");
final int count = getChildCount();
int maxHeight = 0;
int maxWidth = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
Log.i(TAG,"widthMeasureSpec"+widthMeasureSpec+"heightMeasureSpec"+heightMeasureSpec);
}
}
maxWidth += getPaddingLeft() + getPaddingRight();
maxHeight += getPaddingTop() + getPaddingBottom();
Drawable drawable = getBackground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
resolveSize(maxHeight, heightMeasureSpec));
}
private void drawPict(Canvas canvas, int x, int y, int w, int h,
float sx, float sy) {
canvas.save();
canvas.translate(x, y);
canvas.clipRect(0, 0, w, h);
canvas.scale(0.5f, 0.5f);
canvas.scale(sx, sy, w, h);
canvas.drawPicture(mPicture);
canvas.restore();
}
@Override
protected void dispatchDraw(Canvas canvas) {
Log.i(TAG, "dispatchDraw");
super.dispatchDraw(mPicture.beginRecording(getWidth(), getHeight()));
mPicture.endRecording();
int x = getWidth()/2;
int y = getHeight()/2;
if (false) {
canvas.drawPicture(mPicture);
} else {
drawPict(canvas, 0, 0, x, y, 1, 1);
drawPict(canvas, x, 0, x, y, -1, 1);
drawPict(canvas, 0, y, x, y, 1, -1);
drawPict(canvas, x, y, x, y, -1, -1);
}
}
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
Log.i(TAG, "invalidateChildInParent");
location[0] = getLeft();
location[1] = getTop();
dirty.set(0, 0, getWidth(), getHeight());
return getParent();
}
// 在此视图view给他的每一个子元素分配大小和位置时调用。 派生类可以重写此方法并且重新安排他们子类的布局。
// changed 这是当前视图view的一个新的大小或位置
// l 相对于父节点的左边位置
// t 相对于父节点的顶点位置
// r 相对于父节点的右边位置
// b 相对于父节点的底部位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.i(TAG, "onLayout");
final int count = super.getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final int childLeft = getPaddingLeft();
final int childTop = getPaddingTop();
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
}
}
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
Log.i(TAG, "onDraw");
}
@Override
public void draw(Canvas canvas) {
// TODO Auto-generated method stub
super.draw(canvas);
Log.i(TAG, "draw");
}
}
package com.example.mycolomatrix;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
public class GraphicsActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void setContentView(View view) {
if (false) { // set to true to test Picture
ViewGroup vg = new PictureLayout(this);
vg.addView(view);
view = vg;
}
super.setContentView(view);
}
}