Android自定义View基础

自定义控件, 视频教程

http://www.jikexueyuan.com/course/1748.html

1. 编写自定义view

2. 加入逻辑线程

3. 提取和封装自定义view

4. 利用xml中定义样式来影响显示效果

工程代码 DIYControls.zip

----------------------------------

1. 编写自定义view

定义MyView

public class MyView extends View {

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context) {
        super(context);
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        // 加入绘制元素
        Paint paint = new Paint();
        paint.setTextSize(30);
        canvas.drawText("hello carloz", 0, 30, paint); //默认左下对齐
        
    }
}

在布局文件中使用,背景绿色

<com.carloz.diycontrols.MyView 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#00ff00"/>

运行效果

 

绘制几何图形

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

Rect r = new Rect(10, 90, 110, 190); //parm: int
canvas.drawRect(r, paint); // 绘制矩形

RectF rect = new RectF(10, 90, 110, 190); //parm: float
canvas.drawRect(rect, paint); // 绘制矩形

绘制图片

public class MyView extends View {

    Bitmap bitmap;
            
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
    }

    public MyView(Context context) {
        super(context);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        // 加入绘制元素
        Paint paint = new Paint();
        paint.setTextSize(30);
        
        canvas.drawText("hello carloz", 0, 30, paint); //默认左下对齐
        
        RectF r = new RectF(10, 90, 110, 190); //parm: int
        canvas.drawRoundRect(r, 10, 10, paint); // 绘制圆角矩形
        
        paint.setColor(Color.RED); //改变图形颜色
        canvas.drawCircle(60, 270, 50, paint);    // 绘制圆, 圆心, 半径
        
        paint.setStyle(Style.STROKE);  //绘制空心的元素
        
        canvas.drawBitmap(bitmap, 10, 350, paint); //画图
    }
}

 

充当ContentView,可以看到,layout文件中的背景色已经去掉了

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        setContentView(new MyView(this));
    }

}

 

 

2. 加入逻辑线程

目的:让绘制元素动起来

2.1 跑马灯效果的 文字

public class LogicView extends View {

    Paint paint = new Paint();
    String text = "Carloz Logic View";
    private float rx = 0;
    MyThread thread;
    
    public LogicView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    public LogicView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void onDraw(Canvas canvas) {
        paint.setTextSize(30);
        canvas.drawText(text, rx, 30, paint);
        
        if(thread == null ) {
            thread = new MyThread();
            thread.start();
        }
    }
    
    class MyThread extends Thread {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            super.run();
            while(true) {
                rx += 5;
                if (rx > getWidth()) //超出屏幕的时候让它返回来
                    rx = 0 - paint.measureText(text); 
                
                postInvalidate(); //线程中更新绘制,重新调用onDraw方法
                try {
                    Thread.sleep(50); //速度太快肉眼看不到,要睡眠
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}

效果如下,

 

2.2  扇形成圆

 绘制颜色不断变化的圆

public class CircleView extends View {

    Paint paint = new Paint();
    MyThread thread;
    private RectF rectF = new RectF(30, 30, 100, 100);
    private float sweepAngle = 0f; //区间角度
    
    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CircleView(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawArc(rectF, 0, sweepAngle, true, paint);
        //startAngle 起始角度, sweepAngle 区间角度, useCenter
        if(thread == null ) {
            thread = new MyThread();
            thread.start();
        }
    }
    
    class MyThread extends Thread {
        Random rand = new Random();
        @Override
        public void run() {
            // TODO Auto-generated method stub
            super.run();
            while(true) {
                sweepAngle += 2;
                if (sweepAngle > 360) sweepAngle = 0;
                
                int r = rand.nextInt(256); //0~255
                int g = rand.nextInt(256);
                int b = rand.nextInt(256);
                paint.setARGB(255, r, g, b); //透明度为0, 随时改变颜色
                postInvalidate(); //线程中更新绘制,重新调用onDraw方法
                try {
                    Thread.sleep(50); //速度太快肉眼看不到,要睡眠
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}
<com.carloz.diycontrols.logicview.CircleView
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

效果如下,

 

3. 提取和封装自定义view

3.1 简化代码逻辑 -  将操作提取封装成抽象方法,让子类实现

3.2 如何禁止子类修改操作 - 在方法前添加final关键字,不允许子类覆盖

public abstract class BaseView extends View {

    Thread thread;
    
    public BaseView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public BaseView(Context context) {
        super(context);
    }

    @Override
    final protected void onDraw(Canvas canvas) {
        //禁止子类覆盖,用final
        if(thread == null ) {
            thread = new MyThread();
            thread.start();
        } else{
            drawSub(canvas);
        }
    }
    
    protected abstract void logic();
    protected abstract void drawSub(Canvas canvas);

    @Override
    final protected void onDetachedFromWindow() {
        // 离开屏幕时结束
        //onDetachedFromWindow在销毁资源(既销毁view)之后调用
        running = false;
        super.onDetachedFromWindow();
    }
    private boolean running = true;
    class MyThread extends Thread {
        @Override
        public void run() {
            while(running) {
                logic();
    
                postInvalidate(); //线程中更新绘制,重新调用onDraw方法
                try {
                    Thread.sleep(50); //速度太快肉眼看不到,要睡眠
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

}
public class LogicView extends BaseView{
    
    Paint paint = new Paint();
    private RectF rectF = new RectF(30, 30, 100, 100);
    private float sweepAngle = 0; //区间角度
    Random rand = new Random();
    
    public LogicView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    public LogicView(Context context) {
        super(context);
    }

    @Override
    protected void drawSub(Canvas canvas) {
        canvas.drawArc(rectF, 0, sweepAngle, true, paint);
        //startAngle 起始角度, sweepAngle 区间角度, useCenter
    }

    @Override
    protected void logic() {
        sweepAngle += 2;
        if (sweepAngle > 360) sweepAngle = 0;
        
        int r = rand.nextInt(256); //0~255
        int g = rand.nextInt(256);
        int b = rand.nextInt(256);
        paint.setARGB(255, r, g, b); //透明度为0, 随时改变颜色
    }

}

运行如上代码可以看到,效果,与2是一样的

 

4. 利用xml中定义样式来控制显示效果

控制 文字行数,是否滚动

4.1 集成3中的BaseView定义控件 NumText,特别注意两个属性

private int lineNum = 0;
boolean xScroll = false;

//在需要覆盖的方法中,使用属性控制 显示
@Override
protected void logic() {
    if(xScroll) {
        mx += 3;
        if (mx > getWidth())
            mx = 0- paint.measureText(text);
    }
}

@Override
protected void drawSub(Canvas canvas) {
    for(int i=0; i< lineNum; i++){
        int textSize = 30 + i;
        paint.setTextSize(textSize);
        canvas.drawText(text, mx, textSize*(1+i), paint);
    }
}

4.2 在res/values/attrs.xml中定义 styleable

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="NumText">
        <attr name="lineNum" format="integer" />
        <attr name="xScroll" format="boolean" />
    </declare-styleable>
    
</resources>

4.3 在需要使用该 自定义控件 的布局文件中

    * 定义命名空间

        xmlns:carloz="http://schemas.android.com/apk/res/com.carloz.diycontrols"

        · carloz 命名空间,随意

        · http://schemas.android.com/apk/res/ 是固定的

        · com.carloz.diycontrols是包名

    * 使用属性

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:carloz="http://schemas.android.com/apk/res/com.carloz.diycontrols"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/container" >

    <com.carloz.diycontrols.v4.NumText
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        carloz:lineNum="3"
        carloz:xScroll="false" />
    
</FrameLayout>

4.4 在控件的构造函数中 加载 被定义的属性

public NumText(Context context, AttributeSet attrs) {
    super(context, attrs);
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NumText); //获取样式属性
    lineNum = ta.getInt(R.styleable.NumText_lineNum, 1);
    xScroll = ta.getBoolean(R.styleable.NumText_xScroll, false);
    Log.i("ZXQ", "lineNum = " +  lineNum);
    ta.recycle(); //释放TypedArray
}

4.5 最终效果如下

 

工程代码 DIYControls.zip

 

Android的粒子和动画效果系列课程

 

posted @ 2015-08-11 15:43  carlo-z  阅读(410)  评论(0)    收藏  举报