
Subscribe to mihayou

Subscribe to mihayou
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,如果能给各位看官带来一丝启发或者帮助,那真是极好的。
这一篇我们来看自定义View的各种姿势。前面几篇文章中我们介绍了Acitivity的启动流程以及生命周期,还介绍了Activity显示的各种原理。那么这篇文章呢,我们来实战一下。(读者可能看了好多关于Android Activity相关的知识,也看了View的实现原理。可是对于自定义View还是感觉隔着一层膜,那么今天我们试着捅破这层隔膜。) 上篇文章中我们详细讲解了ViewRootImpl,我们知道了其5大过程,知道了View的测量、布局以及绘制。那么这些知识对我们有何用处呢。下面我们就来自定义View
ndroid本身的控件系统可以实现我们开发中的一些基本需求,可是我们在处理实际业务的时候却催生出了Android控件系统不能很好的需求。这时,自定义控件应运而生。
在进行自定义View之前我们先来看一下View的坐标系。
上图引自刘望舒大神的博客
直接继承自View,重写其onDraw方法,这个方式主要用来实现一些不规则的效果。比如显示一个圆。需要注意的是直接继承自View的控件需要对支持wrap_content和padding做处理。所以本例中也重写了onMeasure方法。以及在onDraw方法中加入了自身padding的处理。读者可试着去除onMeasure方法或者onDraw方法中的对padding的处理看看效果
自定义的属性xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="circle_color" format="color" />
</declare-styleable>
</resources>
自定义CircleView
import android.content.Context;
import android.content.res.TypedArray;
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 {
private int mColor = Color.RED;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CircleView(Context context) {
super(context);
init();
}
public CircleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
a.recycle();
init();
}
private void init() {
mPaint.setColor(mColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST
&& heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, 200);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, 200);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingTop - paddingBottom;
int radius = Math.min(width, height) / 2;
canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2,
radius, mPaint);
}
}
使用自定义CircleView 布局文件activity_main1.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical" >
<com.mafeibiao.testapplication.CircleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:background="@color/light_green"/>
</LinearLayout>
MainActivity.java
package com.mafeibiao.testapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main1);
}
}
效果如下图
注:我们在这里直接继承了View并重写其onMeasure和onDraw方法,我们从上几篇文章详细分析了Activity的创建以及显示。我们在梳理一下,*首先程序的入口函数是ActivityThread.main函数,从这个函数开始,然后回调我们MainActivity的attach函数,我们在这里没有重写这个函数,但是该函数内部会创建一个至关重要的对象PhoneWindow,然后会回调我们MainActivity的onCreate函数,我们在MainActivity的onCreate函数中调用了setContentView(R.layout.activity_main1);这个函数内部会创建Android 的顶级View DecorView,把我们的布局文件R.layout.activity_main1解析成相关View并关联到DecorView下。然后会调用WindowManager的addView方法把DecorView添加到PhoneWindow上,实际上完成这个过程的是ViewRootImpl,它会对我们的DecorView依次进测量、布局、绘制等工作,在这些工作的过程中会依次回调我们在View以及其子类中重写的onMeasure、onLayout、onDraw等方法。以我们上面的CircleView为例,,我们在布局文件中定义了一个LinearLayout并在LinearLayout内使用了我们自定义的CircleView,那么按照上一章讲解ViewRootImpl的工作流程。会沿着控件树从上到下*依次调用到我们自定义的CircleView onMeasure(我们重写了该方法)然后沿着控件树从下向上依次回调。上文也讲过,测量过程是后根遍历,布局过程是先根遍历。(要理解Android View的层级结构是树结构**)
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import android.util.Log;
public class CustomTextView extends AppCompatTextView {
private final static String TAG = CustomTextView.class.getSimpleName();
private Paint paint1;
private Paint paint2;
private int mWidth;
private LinearGradient gradient;
private Matrix matrix;
//渐变的速度
private int deltaX;
public CustomTextView(Context context) {
super(context, null);
}
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context, attrs);
}
private void initView(Context context, AttributeSet attrs) {
paint1 = new Paint();
paint1.setColor(getResources().getColor(android.R.color.holo_blue_dark));
paint1.setStyle(Paint.Style.FILL);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(mWidth == 0){
Log.e(TAG,"*********************");
mWidth = getMeasuredWidth();
paint2 = getPaint();
//颜色渐变器
gradient = new LinearGradient(0, 0, mWidth, 0, new int[]{Color.GRAY,Color.WHITE,Color.GRAY}, new float[]{
0.3f,0.5f,1.0f
}, Shader.TileMode.CLAMP);
paint2.setShader(gradient);
matrix = new Matrix();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(matrix !=null){
deltaX += mWidth / 5;
if(deltaX > 2 * mWidth){
deltaX = -mWidth;
}
}
//关键代码通过矩阵的平移实现
matrix.setTranslate(deltaX, 0);
gradient.setLocalMatrix(matrix);
postInvalidateDelayed(100);
}
}
如上图,在作为手机号或者银行卡时输入的数字会按照不同规则分段,并且右侧出现清空按钮。很明显,我们需要自定义一个控件符合上述要求。
style格式attrs.xml
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,如果能给各位看官带来一丝启发或者帮助,那真是极好的。
这一篇我们来看自定义View的各种姿势。前面几篇文章中我们介绍了Acitivity的启动流程以及生命周期,还介绍了Activity显示的各种原理。那么这篇文章呢,我们来实战一下。(读者可能看了好多关于Android Activity相关的知识,也看了View的实现原理。可是对于自定义View还是感觉隔着一层膜,那么今天我们试着捅破这层隔膜。) 上篇文章中我们详细讲解了ViewRootImpl,我们知道了其5大过程,知道了View的测量、布局以及绘制。那么这些知识对我们有何用处呢。下面我们就来自定义View
ndroid本身的控件系统可以实现我们开发中的一些基本需求,可是我们在处理实际业务的时候却催生出了Android控件系统不能很好的需求。这时,自定义控件应运而生。
在进行自定义View之前我们先来看一下View的坐标系。
上图引自刘望舒大神的博客
直接继承自View,重写其onDraw方法,这个方式主要用来实现一些不规则的效果。比如显示一个圆。需要注意的是直接继承自View的控件需要对支持wrap_content和padding做处理。所以本例中也重写了onMeasure方法。以及在onDraw方法中加入了自身padding的处理。读者可试着去除onMeasure方法或者onDraw方法中的对padding的处理看看效果
自定义的属性xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="circle_color" format="color" />
</declare-styleable>
</resources>
自定义CircleView
import android.content.Context;
import android.content.res.TypedArray;
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 {
private int mColor = Color.RED;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CircleView(Context context) {
super(context);
init();
}
public CircleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
a.recycle();
init();
}
private void init() {
mPaint.setColor(mColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST
&& heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, 200);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, 200);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingTop - paddingBottom;
int radius = Math.min(width, height) / 2;
canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2,
radius, mPaint);
}
}
使用自定义CircleView 布局文件activity_main1.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical" >
<com.mafeibiao.testapplication.CircleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:background="@color/light_green"/>
</LinearLayout>
MainActivity.java
package com.mafeibiao.testapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main1);
}
}
效果如下图
注:我们在这里直接继承了View并重写其onMeasure和onDraw方法,我们从上几篇文章详细分析了Activity的创建以及显示。我们在梳理一下,*首先程序的入口函数是ActivityThread.main函数,从这个函数开始,然后回调我们MainActivity的attach函数,我们在这里没有重写这个函数,但是该函数内部会创建一个至关重要的对象PhoneWindow,然后会回调我们MainActivity的onCreate函数,我们在MainActivity的onCreate函数中调用了setContentView(R.layout.activity_main1);这个函数内部会创建Android 的顶级View DecorView,把我们的布局文件R.layout.activity_main1解析成相关View并关联到DecorView下。然后会调用WindowManager的addView方法把DecorView添加到PhoneWindow上,实际上完成这个过程的是ViewRootImpl,它会对我们的DecorView依次进测量、布局、绘制等工作,在这些工作的过程中会依次回调我们在View以及其子类中重写的onMeasure、onLayout、onDraw等方法。以我们上面的CircleView为例,,我们在布局文件中定义了一个LinearLayout并在LinearLayout内使用了我们自定义的CircleView,那么按照上一章讲解ViewRootImpl的工作流程。会沿着控件树从上到下*依次调用到我们自定义的CircleView onMeasure(我们重写了该方法)然后沿着控件树从下向上依次回调。上文也讲过,测量过程是后根遍历,布局过程是先根遍历。(要理解Android View的层级结构是树结构**)
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import android.util.Log;
public class CustomTextView extends AppCompatTextView {
private final static String TAG = CustomTextView.class.getSimpleName();
private Paint paint1;
private Paint paint2;
private int mWidth;
private LinearGradient gradient;
private Matrix matrix;
//渐变的速度
private int deltaX;
public CustomTextView(Context context) {
super(context, null);
}
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context, attrs);
}
private void initView(Context context, AttributeSet attrs) {
paint1 = new Paint();
paint1.setColor(getResources().getColor(android.R.color.holo_blue_dark));
paint1.setStyle(Paint.Style.FILL);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(mWidth == 0){
Log.e(TAG,"*********************");
mWidth = getMeasuredWidth();
paint2 = getPaint();
//颜色渐变器
gradient = new LinearGradient(0, 0, mWidth, 0, new int[]{Color.GRAY,Color.WHITE,Color.GRAY}, new float[]{
0.3f,0.5f,1.0f
}, Shader.TileMode.CLAMP);
paint2.setShader(gradient);
matrix = new Matrix();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(matrix !=null){
deltaX += mWidth / 5;
if(deltaX > 2 * mWidth){
deltaX = -mWidth;
}
}
//关键代码通过矩阵的平移实现
matrix.setTranslate(deltaX, 0);
gradient.setLocalMatrix(matrix);
postInvalidateDelayed(100);
}
}
如上图,在作为手机号或者银行卡时输入的数字会按照不同规则分段,并且右侧出现清空按钮。很明显,我们需要自定义一个控件符合上述要求。
style格式attrs.xml
No activity yet