对我来说,写自定义view是一个特麻烦但是写完之后特有成就感的过程。写完之后我总是喜欢拿给别人看,去炫耀(当然只是在自己熟悉和关系不错的人群里),尽管它们看起来会很简陋。希望这次写的东西不会让大家觉得太过于简陋,有错误的还是得需要各大神的点醒和赐教,有批评建议的也请随时赐教。
一般的自定义view有三种方式:
- 继承具体的view,如textView,button等等;
- 继承一个容器view,如linearlayout,relativeLayout等等,然后给该容器添加一些具体的控件来组合成一个新的view;
- 继承View。这个方式需要重写包括onDraw,onMeasure,onLayout,onTouchEvent等等方法来绘制一个新的view
对于这三种方式,我在这里说一下自己的看法:
首先,对于我来说,我很少使用第一种方式。由于继承的是一个具体的view,这个view的功能一般都是很强大了。我们能做的,希望去做的,大概是改变形状或者添加一些形状功能等等。当然由于我目前基本上没有做过相应的需求,我也无法去说这个方式到底该怎么弄。因为在我目前认知里,如果准备大改某个view,我会用第三种方式,如果需要添加一些模块或者功能我会使用第二种方式。
然后,对于第二种方式,简单得说,就是控件组合,把一些控件组合起来,形成一个特殊形状和功能的view。这个是三种方式中我觉得最方便的,只要做好各个控件的摆放,并提供一些参数设置的方法,或者干脆把这些控件实例提供给使用者,让使用者自己去操作这些实例。这个方法的缺点也是有的,这里说的特殊形状,是由有规则的控件布局而成,所能表达的形状也是有限的。
对于第三种方式,简单地说,就是自己画,利用canvas和paint等一点一线的自己来画。这种方式是最具创造力的,可以画出几乎所有的形状。(我这里这么说,是我感觉这种方式最贴近自定义这个词)。但同时他的缺点也是巨大的,计算绘制的点、线、框、文字等等位置的值,是这个方式最大的挑战之一。我经常就发现,在我程序的某段里,全都是成员变量在那里加减乘除,如果不给成员变量添加/* ……/这样的注释,过不了多久就不知道那段程序到底在干什么。
对于这三种方式,第一种我几乎不用,在时间不够充裕的时候我会用第二种方式,在时间充裕的时候和view非常特殊需要自己绘制的时候我会使用第三种方式。
接下来我把我写过的一个类似seekBar的自定义view拿出来和大家探讨一下。这是一个非常简单的自定义view,就是我们常见的视频播放控件下边显示视频进度的一个进度条,不同的是他的左右各有一个textView来显示时间。
第二种方式(组合)创建自定义view
我用第二种方式写的代码:
自定义view:
package test2;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
public class MySeekbarView extends LinearLayout{
private TextView mTvCurrentTime;
private TextView mTvTotalTime;
private SeekBar mSeekbar;
public MySeekbarView(Context context, AttributeSet attrs) {
super(context, attrs);
LinearLayout ll = new LinearLayout(context);
ll.setGravity(Gravity.CENTER_VERTICAL);
ll.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mTvCurrentTime = new TextView(context);
mTvCurrentTime.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
mTvTotalTime = new TextView(context);
mTvCurrentTime.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
mSeekbar = new SeekBar(context);
mSeekbar.setLayoutParams(new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1));
ll.addView(mTvCurrentTime);
ll.addView(mSeekbar);
ll.addView(mTvTotalTime);
this.addView(ll);
}
public TextView getCurrentTimeTextView(){
return mTvCurrentTime;
}
public TextView getTotalTimeTextView(){
return mTvTotalTime;
}
public SeekBar getSeekbar(){
return mSeekbar;
}
}
Activity的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#ffff00">
<test2.MySeekbarView
android:id="@+id/MySeekbarView"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#ff00ff"
/>
</LinearLayout>
Activity:
package test2;
import com.example.test2.R;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.widget.SeekBar;
import android.widget.TextView;
public class Test2_seekbar extends Activity{
private MySeekbarView view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test2_seekbar_layout);
view = (MySeekbarView)findViewById(R.id.MySeekbarView);
TextView tv1 = view.getCurrentTimeTextView();
tv1.setText("01:00");
TextView tv2 = view.getTotalTimeTextView();
tv2.setText("09:00");
SeekBar seekbar = view.getSeekbar();
seekbar.setThumb(ContextCompat.getDrawable(this, R.drawable.video_pb_indicator));
seekbar.setProgress(50);
}
}
结果:
有没有觉得很方便?在view的数量不多的情况下,直接新建view然后用addView()很方便快捷,但在view的数量有点大或者觉得脑子里形象不是很直观的情况下,我们一般会写一个布局然后通过LayoutInflater的
inflate方法来直接获得自己想要的这个已经布局好的view。
自定义view布局的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"
android:textSize="16sp"
/>
<SeekBar
android:id="@+id/seekbar"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:thumb="@drawable/video_pb_indicator"
/>
<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="55:55"
android:textSize="16sp"
/>
</LinearLayout>
修改之后的自定义view:
package test2;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import com.example.test2.R;
public class MySeekbarView extends LinearLayout{
private TextView mTvCurrentTime;
private TextView mTvTotalTime;
private SeekBar mSeekbar;
private View view;
public MySeekbarView(Context context, AttributeSet attrs) {
super(context, attrs);
view = LayoutInflater.from(context).inflate(R.layout.myseekbar_view_layout, this);
mTvCurrentTime = (TextView)view.findViewById(R.id.tv1);
mTvTotalTime = (TextView)view.findViewById(R.id.tv2);
mSeekbar = (SeekBar)view.findViewById(R.id.seekbar);
}
public TextView getCurrentTimeTextView(){
return mTvCurrentTime;
}
public TextView getTotalTimeTextView(){
return mTvTotalTime;
}
public SeekBar getSeekbar(){
return mSeekbar;
}
}
结果是一样。
有没有发现,哪里不一样。哈哈,发没发现我都要讲了。这是一个关于LayoutInflater的故事。我是在这个地方获得了很厚重的知识:Android LayoutInflater深度解析 给你带来全新的认识。
这里边博主告诉我们LayoutInflater的Inflate的三个重构方法的差别:
Inflate(resId , null ):返回temp,没有执行setLayoutParams和addView
Inflate(resId , root,false ):返回temp,执行setLayoutParams,没有执行addView
Inflate(resId , root, true ):返回root,执行addView(view,params)
(呃在我那个程序中看到Inflate(resId , root)后没有addView()方法也可以正常显示,说明第三个参数默认为true。)
这样看来在使用Inflate方法的时候有三种选择:
1.
temp = LayoutInflater.from(context).Inflate(resId , null );
temp.setLayoutParams(…);
this.addView(temp);
2.
temp = LayoutInflater.from(context).Inflate(resId , root,false );
this.addView(temp);
3.
LayoutInflater.from(context).Inflate(resId , root,true );
可想而知他们之间的差别:
3是最简单的情况,添加单个temp,无特殊params要求;
2其次,可以添加多个temp,无特殊params要求;
1最麻烦,既可以添加多个view又可以添加特殊params要求,唯一不爽的是,需要给每个temp都添加一遍;
这里我还得提醒一下各位兄弟姐妹们(我最近在自定义的时候犯的错误,好不容易才找出来,希望各位兄姐妹们看了以后不要笑,也希望你们不会遇到这样的问题):
1.一定要尽量把那几个构造方法的重构都写上。其实以我目前的经验,对我有用的重构也就只有public MySeekbarView(Context context)和public MySeekbarView(Context context, AttributeSet attrs)。第一个我觉得主要是在随时随地通过context新建view的时候调用,第二个我觉得主要是在利用layout布局来创建view的时候调用。inflate方法中设置的layoutParams是从attrs中得到的,构造函数中没有传入AttributeSet attrs或者传入的为空,inflate中设置的layoutParams为null,然后你就会看见一片空白;
2.尽量利用布局创建view;
3.不通过布局创建view的时候一定不要忘记setLayoutParams,通过布局创建view但没有设置layoutParams时也要记得添加layoutParams;
4.这些对于linearlayout和relativelayout是有效的,对于viewGroup,呃,我并不是很确定,以后细细研究过后再来探讨,不过我认为linearlayout和relativelayout已经包含了很多内容;
第三种方式(draw)创建自定义view
是不是有些乱了?我只能说乱的还在后边。接下来我们来交流一下用第三种方法来自定义view。
package mydemo.myseekbardemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
public class MyView extends View{
private Context mContext;
/**
* view的宽
*/
private int mViewWidth;
/**
* view的高
*/
private int mViewHeight;
/**
* 底部进度条的宽
*/
private int mBottomBarWidth;
/**
* 进度条的高
*/
private int mBarHeight = 6;
/**
* 底部进度条左边位置
*/
private int mBottomBarLeft;
/**
* 底部进度条上边位置
*/
private int mBottomBarTop;
/**
* 底部进度条右边位置
*/
private int mBottomBarRight;
/**
* 底部进度条底边位置
*/
private int mBottomBarBottom;
/**
* 顶部进度条的右边位置
*/
private int mTopBarRight;
/**
* 文字的宽
*/
private int mTvWidth;
/**
* 文字的高
*/
private int mTvHeight;
/**
* 图片对象
*/
private Bitmap mBitmapOfCirlcle = null;
/**
* 图片可见度,在0~255之间
*/
private int mBitmapAlpha = 255;
/**
* 规定图片的宽
*/
private int mBitmapWidth = 30;
/**
* 规定图片的高
*/
private int mBitmapHeight = 30;
/**
* 画笔对象
*/
private Paint mPaint;
/**
* 判断是否使用用户的图片
*/
private boolean hasCirclePic = false;
/**
* 圆圈圆心的x坐标
*/
private int mCircleX;
/**
* 圆圈圆心的y坐标
*/
private int mCircleY;
/**
* 文字大小
*/
private float mTextSize = 35;
/**
* 一些间距
*/
private int mPadding = 10;
/**
* 圆圈的半径
*/
private int mRadius = 16;
/**
* 顶部进度条的右边位置同时也是圆圈的x坐标
*/
private int mPosition;
/**
* 顶部进度条初始百分比进度
*/
private float mOriginalPercent = 0f;
/**
* 文本的颜色
*/
private int mTextColor = Color.WHITE;
/**
* 底部进度条的颜色
*/
private int mBottomBarColor = Color.GRAY;
/**
* 顶部进度条的颜色
*/
private int mTopBarColor = Color.BLUE;
/**
* 圆圈的颜色
*/
private int mCircleColor = Color.WHITE;
/**
* 用户点击位置状态
*/
private int mWhereClickedState;
/**
* 用户点击在圆圈上
*/
private final int WHERR_CLICKED_CIRCLE = 0;
/**
* 用户点击在进度条上
*/
private final int WHERR_CLICKED_BAR = 1;
/**
* 用户点击在其他部分
*/
private final int WHERR_CLICKED_VIEW = 2;
/**
* 实时时间文本
*/
private String mCurrentTimeString = "00:00";
/**
* 总时间文本
*/
private String mTotalTimeString = "00:00";
/**
* 手势管理对象
*/
private GestureDetector mGestureDetector;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i("tag", "MyView");
mContext = context;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mGestureDetector = new GestureDetector(mContext, new OnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
if(mWhereClickedState == WHERR_CLICKED_CIRCLE){
}else if(mWhereClickedState == WHERR_CLICKED_BAR){
mPosition = (int) Math.round(e.getX()+0.5);
if(mPosition >= mBottomBarRight){
mPosition = mBottomBarRight;
}else if(mPosition <= mBottomBarLeft){
mPosition = mBottomBarLeft;
}
MyView.this.invalidate();
}
return false;
}
@Override
public void onShowPress(MotionEvent e) {
// TODO Auto-generated method stub
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
if(mWhereClickedState == WHERR_CLICKED_CIRCLE){
mPosition = (int) Math.round(e2.getX()+0.5);
if(mPosition >= mBottomBarRight){
mPosition = mBottomBarRight;
}else if(mPosition <= mBottomBarLeft){
mPosition = mBottomBarLeft;
}
MyView.this.invalidate();
}else if(mWhereClickedState == WHERR_CLICKED_BAR){
}
return false;
}
@Override
public void onLongPress(MotionEvent e) {
// TODO Auto-generated method stub
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onDown(MotionEvent e) {
float event_x = e.getX();
float event_y = e.getY();
mWhereClickedState = judgeWhereClicked(event_x, event_y);
if(mWhereClickedState == WHERR_CLICKED_VIEW){
return false;
}else{
return true;
}
}
});
}
/**
* 获得文本的宽高
*/
private void initTvWidthAndHeight() {
Rect rect = new Rect();
mPaint.getTextBounds("00:00", 0, 5, rect);
mTvWidth = rect.width();
mTvHeight = rect.height();
}
@Override
protected void onDraw(Canvas canvas) {
Log.i("tag", "onDraw");
mTopBarRight = mPosition;
mCircleX = mTopBarRight;
mPaint.setColor(mTextColor);
canvas.drawText(mCurrentTimeString, mPadding, mBottomBarBottom+Math.round(mTvHeight/4.0+0.5), mPaint);
canvas.drawText(mTotalTimeString, mViewWidth - mTvWidth - mPadding, mBottomBarBottom+Math.round(mTvHeight/4.0+0.5), mPaint);
mPaint.setColor(mBottomBarColor);
canvas.drawRect(mBottomBarLeft, mBottomBarTop, mBottomBarRight, mBottomBarBottom, mPaint);
mPaint.setColor(mTopBarColor);
canvas.drawRect(mBottomBarLeft, mBottomBarTop, mTopBarRight, mBottomBarBottom, mPaint);
if(mBitmapOfCirlcle != null){
mPaint.setAlpha(mBitmapAlpha);
canvas.drawBitmap(mBitmapOfCirlcle, mCircleX-mBitmapWidth/2, mCircleY - mBitmapHeight/2, mPaint);
}else{
mPaint.setColor(mCircleColor);
canvas.drawCircle(mCircleX, mCircleY, mRadius, mPaint);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mPaint.setTextSize(mTextSize);
//获得文本的宽高
initTvWidthAndHeight();
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if(widthMode == MeasureSpec.AT_MOST){
mViewWidth = mTvWidth*4;
}else if(widthMode == MeasureSpec.EXACTLY){
if(mTvWidth*4 >= widthSize){
mViewWidth = mTvWidth*4;
}else{
mViewWidth = widthSize;
}
}
widthMeasureSpec = MeasureSpec.makeMeasureSpec(mViewWidth, MeasureSpec.EXACTLY);
if(heightMode == MeasureSpec.AT_MOST){
mViewHeight = mTvHeight + 4*mPadding;
}else if(heightMode == MeasureSpec.EXACTLY){
if(heightSize <= mTvHeight+4*mPadding ){
mViewHeight = mTvHeight+4*mPadding;
}else{
mViewHeight = heightSize;
}
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(mViewHeight, MeasureSpec.EXACTLY);
mBottomBarWidth = mViewWidth - 2*mTvWidth - 6*mPadding;
mBottomBarLeft = mTvWidth + 3*mPadding;
mBottomBarRight = mViewWidth - mTvWidth - 3*mPadding;
mBottomBarBottom = (mViewHeight - mTvHeight)/2+mTvHeight;
mBottomBarTop = mBottomBarBottom-mBarHeight;
mCircleY = mBottomBarBottom - mBarHeight/2;
mPosition = (int) Math.round(mBottomBarWidth * mOriginalPercent+0.5)+mBottomBarLeft;
if(mPosition <= mBottomBarLeft){
mPosition = mBottomBarLeft;
}else if(mPosition >= mBottomBarRight){
mPosition = mBottomBarRight;
}
if(mBitmapOfCirlcle != null){
mBitmapOfCirlcle = Bitmap.createScaledBitmap(mBitmapOfCirlcle, mBitmapWidth, mBitmapHeight, false);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 设置总时间,默认格式"--:--"
* @param strOfTime
*/
public void setTotalTimeString(String strOfTime){
Log.i("tag", "setTotalTimeString");
mTotalTimeString = strOfTime;
}
/**
* 设置实时时间,默认格式"--:--"
* @param strOfTime
*/
public void setCurrentTimeString(String strOfTime){
Log.i("tag", "setCurrentTimeString");
mCurrentTimeString = strOfTime;
}
/**
* 设置图片的透明度
* @param alpha 0~255
*/
public void setBitmapAlpha(int alpha){
Log.i("tag", "setBitmapAlpha");
if(alpha < 0){
alpha = 0;
}else if(alpha > 255){
alpha = 255;
}
mBitmapAlpha = alpha;
}
/**
* 获得现在所在位置的百分比进度值
* @return float
*/
public float getPercent(){
float percent = (mBottomBarRight - mPosition)*1.0f/mBottomBarWidth;
if(percent < 0.0){
percent = 0f;
}else if(percent > 1){
percent = 1f;
}
return percent;
}
/**
* 获得当前进度值
* @return int
*/
public int getProgress(){
float percent = (mBottomBarRight - mPosition)*1.0f/mBottomBarWidth;
if(percent < 0.0){
percent = 0f;
}else if(percent > 1){
percent = 1f;
}
int progress = (int) Math.round(percent*100+0.5);
if(progress < 0){
progress = 0;
}else if(progress >100){
progress = 100;
}
return progress;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(mGestureDetector != null){
return mGestureDetector.onTouchEvent(event);
}else{
return super.onTouchEvent(event);
}
}
/**
* 判断用户点击位置状态
* @param x 用户点击的x坐标
* @param y 用户点击的y坐标
* @return 返回用户点击未知状态:
* WHERR_CLICKED_CIRCLE
* WHERR_CLICKED_BAR
* WHERR_CLICKED_VIEW
*/
public int judgeWhereClicked(float x, float y){
//s_x,s_y用来界定圆圈或者代替圆圈的图片的区域,s_x表示x坐标方向,圆心到区域边界的距离
int s_x = 0;
int s_y = 0;
if(hasCirclePic){
s_x = mBitmapWidth/2;
s_y = mBitmapHeight/2;
}else{
s_x = mRadius;
s_y = mRadius;
}
if( (x>=(mCircleX-s_x) && x<=(mCircleX + s_x)) && (y>=(mCircleY-s_y) && y<=(mCircleY + s_y)) ){
//s_x和s_y界定的区域,即圆圈区域
return WHERR_CLICKED_CIRCLE;
}else if((x>=mBottomBarLeft && x<=mBottomBarRight) || (y <= mBottomBarBottom+5 && y >= mBottomBarTop+5 )){
//进度条上除了圆圈区域的其他可响应点击部分
return WHERR_CLICKED_BAR;
}else{
//view除了上述两部分的部分
return WHERR_CLICKED_VIEW;
}
}
/**
* 设置初始百分比进度,0.0~1.0f
* @param percent
*/
public void setOriginalPercent(float percent){
mOriginalPercent = percent;
if(mOriginalPercent<0){
mOriginalPercent = 0.0f;
}else if(mOriginalPercent > 1.0f){
mOriginalPercent = 1.0f;
}
}
/**
* 设置初始进度值,默认进度最大值为100
* @param progress
*/
public void setOriginalProgress(int progress){
mOriginalPercent = progress/100f;
if(mOriginalPercent<0){
mOriginalPercent = 0.0f;
}else if(mOriginalPercent > 1.0f){
mOriginalPercent = 1.0f;
}
}
/**
* 通过传入进度百分比来更新进度条
* @param percent
*/
public void updatePositionFromPercent(float percent){
Log.i("tag", "updatePositionFromPercent");
if(percent<0){
percent = 0.0f;
}else if(percent > 1.0f){
percent = 1.0f;
}
mPosition = (int) Math.round(mBottomBarWidth * percent+0.5)+mBottomBarLeft;
if(mPosition <= mBottomBarLeft){
mPosition = mBottomBarLeft;
}else if(mPosition >= mBottomBarRight){
mPosition = mBottomBarRight;
}
this.invalidate();
}
/**
* 通过传入进度值来更新进度条
* @param percent
*/
public void updatePositionFromProgress(int progress){
float percent = progress/100f;
if(percent<0){
percent = 0.0f;
}else if(percent > 1.0f){
percent = 1.0f;
}
mPosition = (int) Math.round(mBottomBarWidth * percent+0.5)+mBottomBarLeft;
if(mPosition <= mBottomBarLeft){
mPosition = mBottomBarLeft;
}else if(mPosition >= mBottomBarRight){
mPosition = mBottomBarRight;
}
this.invalidate();
}
/**
* 设置文字颜色
* @param color
*/
public void setTextColor(int color){
mTextColor = color;
}
/**
* 设置文字大小
* @param size
*/
public void setTextSize(int size){
mTextSize = size;
}
/**
* 设置底部进度条颜色
* @param color
*/
public void setBottomBarColor(int color){
mBottomBarColor = color;
}
/**
* 设置顶部进度条颜色
* @param color
*/
public void setTopBarColor(int color){
mTopBarColor = color;
}
/**
* 获得实时时间文本
* @return
*/
public String getCurrentTimeString(){
return mCurrentTimeString;
}
/**
* 获得总时间文本
* @return
*/
public String getTotalTimeString(){
return mTotalTimeString;
}
/**
* 用其他图片更换进度条上的圆圈
* @param bitmap
*/
public void setThumbBitmap(Bitmap bitmap){
mBitmapOfCirlcle = bitmap;
}
}
哈哈,是不是感觉有点头大。其实很简单,一共两个部分,一是计算位置,二是绘制图形。其中最看重的就是位置的计算,位置计算好了,绘制就是分分钟的事了。canvas的绘制功能很强大,一切形状功能都可以轻松绘制出来。看着图形从一个点一条线到一个复杂图形甚至是拥有动画属性,这个过程是很有成就感的,也是我最喜欢的过程。
对canvas和paint了解还不深的我就不在这里露短了,只想告诉大家一些我知道的需要注意的问题:
1.现在的手机基本上都是支持硬件加速的,而并不是所有的canvas的操作都支持硬件加速的,比如canvas.drawPicture()等,所以在使用自定义view特别是自己绘制的自定义view的时候需要注意是否应该开启硬件加速;
2.最好不要频繁得创建paint对象,频繁创建对象本就是编程的大忌,绘制的过程更是如此;
3.想要使得view拥有动画属性,只要在静止的位置上添加偏移量,改变偏移量,就能完成动画的绘制;
4.尽量给成员变量添加/** ……/这样的注释,特别是在计算位置特别复杂的自定义view里,这样会更加便于阅读和回忆;
第一次写博客,没想到会花这么长的时间。以前觉得博客内容很少,写的应该蛮快的。今天我真真切切的感受到,写一篇博客真是相当不容易的。也许只是像我这种小新才会有这种感觉把。不过还蛮有成就感的,尽管我觉得真的没有写些什么。最后还是希望有什么批评建议的请随时赐教!