自定义View <0> 继承现有的控件

自定义View的 第一种形式继承现有的UI控件:实现特定功能,例如事件拦截,重新绘制。(继承某个控件,例如EditText,需要两个构造方法)

0:自定义View的步骤和使用:

继承View或者View的子类
声明构造方法
View(Context c) 这个构造方法在代码中创建控件的时候使用
View(Context ct,AttributeSet set)这个构造在布局xml文件中创建控件的时候自动的调用,如果自定义控件没有这个构造方法,就会抱错。报错地址:http://blog.csdn.net/rodulf/article/details/50915600,这个含有AttributeSet 的构造方法,应用于布局创建控件,这个AttribteSet attrs 就是XML里面的属性,通过这个属性传给控件,不然控件怎么知道高度时多少,宽度是多少,


1:继承已有的控件的方式
    如果要拦截,就重写onInterceptTouchEvent

    如果要重新绘制,那么就是重写onDraw()方法,onDraw方法是一个回调方法,当Android 要显示当前的控件到屏幕上的时候,就会回调这个方法,让控件自己把自己长什么样子画到屏幕上
canvas就是画布的意思,当控件在画布上面画完之后,最后由系统贴到屏幕上,记住是贴到屏幕上面的
如果上面的onDraw里面删除了super.onDraw(canvas)的话,是不会显示的,调整控件的显示:通过空间的onDraw 方法来修改,super.onDraw(canvas) 代表原有的空间显示方式;
canvas.drawArc可以用来画饼图
        canvas.drawBitmap();可以切图,按照等比切,drawBitmap还可以用来做穿衣和试衣的软件

/!!! 记住了一定不能在onDraw方法里面进行对象的创建,这样非常影响性能。

2:



+++++++++++

1: 游戏,股票,涂鸦
2:办公统计,柱状统计。
3: 电子书
4:听力测试


像大神一样写代码
-----------------------------------------
提升的地方:-----------------------------
-----------------------------------------
内置的UI空间和布局无法满足需求的时候,就需要进行外观,操作都是自定义的一些控件,
这个时候就要进行自定义View


-----------------------------------------
自定义View 的方式:
1)继承现有的UI空间:实现特定功能
2)将多个空间组合,形成新的自定义View:瀑布流,Radio动态指示器
3)完全自定义回执:自己来话出来外观,自己实现事件


自定义View 的步骤和使用:
1:继承View 或者View 的子类
2:申明构造方法
3:View(Context c)这个构造在代码中创建空间的时候使用
4:View(Context ct,AttributeSet set)这个构造在布局xml 文件中创建空间的时候自动调用。
如果自定义空间没有博啊汗带有AttributeSet的参数的构造方法,就会报异常。








新建一个CusomerView1


创建一个包widget
新建一个java类SimpleView




public class SimpleView extends EditText {


    /**
     * 任何控件,只有一个参数的构造方法,应用于代码创建控件
     * 例如 ImageView imageView = new ImageView(context);
     * @param context
     */
    public SimpleView(Context context) {
        super(context);
    }


    /**
     * 包含AttributeSet 的构造方法,应用于在布局文件中加载控件的情况。
     * AttributeSet 实际上就是包含了布局中的XML指定的属性;通过这个属性传给空间。
     * 这个方法在布局中创建空间的时候,是必须要调用和存在的,不然会抛出异常。
     * @param context
     * @param atts
     */
    public SimpleView(Context context,AttributeSet atts){
        super(context,atts);
    }
}




main 的xml


<!--包含自定义空间或者第三方控件或者Android Support 中的空间
    都可以通过类的全路劲来包含
    -->
    <com.kodulf.customerview1.widget.SimpleView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#EE3344"
        android:hint="请输入您的内容"/>






修改:
public class SimpleView extends LinearLayout {




<com.kodulf.customerview1.widget.SimpleView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:text="自定义View 里面添加一个View"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            />
        </com.kodulf.customerview1.widget.SimpleView>




在修改
public class SimpleView extends LinearLayout {




   <com.kodulf.customerview1.widget.SimpleView
            android:layout_width="match_parent"
            android:layout_height="_match_parent"
            android:background="#EE3344"/>




早上第二课
----------------------------------------------
----------------------------------------------
新建NotePad java 
shift+F6 修改名字




/**
 * 自定义View 案例1:继承已有空间的方式,实现记事本输入界面:
 */
public class NotePadView extends EditText{
    public NotePadView(Context context){
        //super(context);
        //通常,一个参数的构造,可以调用自身两个参数的
        this(context,null);
    }
    public NotePadView(Context context, AttributeSet attrs){
        super(context,attrs);
    }
}-------------------------------------------------------


重写onDraw()方法


去韩国,按照某个图片去话,这个图片就是onDraw();




    /**
     * 这个方法是一个回调方法,当Android 要显示当前控件到屏幕上的时候
     * 就会调用这个额方法啊,让控件自己把自己长什么样子画到屏幕上
     *
     * @param canvas Canvas 相当于一个画布,当空间在画布上面画完之后,最后由系统贴到屏幕上
     *               记住是贴到屏幕上的。
     */
    @Override
    protected void onDraw(Canvas canvas) {
        
        super.onDraw(canvas);
        //TODO: 绘制内容:


    }


如果上面的onDraw里面删除了super.onDraw(canvas)的话,是不会显示的


调整控件的显示:通过空间的onDraw 方法来修改
super.onDraw(canvas) 代表原有的空间显示方式;
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
drawArc()还可以画饼图,drawBitmap还可以切图,按照等比切,
                        //drawBitmap穿衣试衣的软件,还可以
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
//android 代码当中,所有的和尺寸,坐标相关的单位,都是像素px
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


Carvas 绘制的时候,都需要设置Paint,


/!!!!注意android 控件的onDraw方法,是进制创建任何对象的;


光标显示一次,创建一次,所以不允许创建任何对象。


设置成员变量Paint


/**
     * 用于控制画线的样式;
     * 通常不要再onDraw创建,
     * 需要创建一个单独的初始化方法,
     */
    private Paint linePaint;






    /**
     * 用于初始化绘制时使用的各种对象数据;
     * @param context
     * @param set
     */
private void init(Context context,AttributeSet set){
            linePaint = new Paint();
linePaint.setColor(Color.RED);
    }


然后再在构造方法调用;
public NotePadView(Context context, AttributeSet attrs){
        super(context, attrs);
        init(context,attrs);
    }




更新:


protected void onDraw(Canvas canvas) {
        //对于已有空间而言,super.onDraw 代表原有内容的显示;
        super.onDraw(canvas);
        //TODO: 绘制内容:只要使用Canvase的绘制,就可以实现显示了;
        //Android 控件中,自身的左上角永远是(0,0)
        //右下角 就是(width,height)坐标轴
        //android 的整个屏幕,也是左上角(0,0)
        //android 代码当中,所有的和尺寸,坐标相关的单位,都是像素px
        //从(startX,startY)到(endX,endY)
        //!!!!注意android 控件的onDraw方法,是进制创建任何对象的;
        canvas.drawLine(
                0,//startX
                50,//startY
                200,//endX
                50,
        linePaint);//endY


                //drawArc()还可以画饼图,drawBitmap还可以切图,按照等比切,
                        //drawBitmap穿衣试衣的软件,还可以




    }


更新:
以空间的宽度来画:


        //继承已有空间:充分利用以后空间的方法和属性,完成功能
        //1.获取控件的宽度,
        int width =getWidth();
        canvas.drawLine(0,50,width,50,linePaint);




更新:
        //继承已有空间:充分利用以后空间的方法和属性,完成功能
        //1.获取控件的宽度,
        int width =getWidth();


        //2.对于EditText子类而言,需要获取一行的文本的高度,
        //因为可以设置textSize,通过 getLineHeight();来得到
        int lineHeight = getLineHeight();


        canvas.drawLine(0, lineHeight, width, lineHeight, linePaint);


这个时候会出现,会出现线在字体上面


解决方法如下:
android:background="#FFF"
//3. 在绘制的时候,需要考虑一个空间内部的padding 信息
        // 默认的情况下EditText包含顶部Padding






空间绘制的时候的注意事项:
1:需要考虑空间自身的padding,padding 影响了计算坐标的位置


更新:
//1.获取控件的宽度,
        int width =getWidth();


        //2.对于EditText子类而言,需要获取一行的文本的高度,
        //因为可以设置textSize,通过 getLineHeight();来得到
        int lineHeight = getLineHeight();
        //3. 在绘制的时候,需要考虑一个空间内部的padding 信息
        // 默认的情况下EditText包含顶部Padding
        //设置android:background="#FFF" 可以解决
        int paddingTop = getPaddingTop();


        canvas.drawLine(0, lineHeight+paddingTop, width, lineHeight+paddingTop, linePaint);
   


更新:


        //4。根据行数来绘制线段,
        //获取当当前输入框,实际内容的行数,
        int lineCount = getLineCount();
        for(int i=0;i<lineCount;i++){
            canvas.drawLine(0,paddingTop+lineHeight*(i+1),width,paddingTop+lineHeight*(i+1),linePaint);
        }


UPDATE:
int lineCount = getLineCount();
        for(int i=0;i<lineCount;i++){
            int lineY = paddingTop + lineHeight * (i + 1);
            canvas.drawLine(0, lineY,width, lineY,linePaint);
        }


更新:


//5. 获取高度,来第一次绘制的时候就绘制整个屏幕,需要减去paddingtop 和padding bottom
        int height = getHeight();
        //获取padding bottom 计算实际内容高度
        int paddingBottom = getPaddingBottom();
        int num = (height - paddingBottom - paddingTop)/lineHeight;
        int lineCount = getLineCount();
        lineCount = Math.max(lineCount,num);


        for(int i=0;i<lineCount;i++){
            int lineY = paddingTop + lineHeight * (i + 1);
            canvas.drawLine(0, lineY,width, lineY,linePaint);
        }




下午:
------------------------------------------------------------
------------------------------------------------------------
在onDraw方法,不允许创建对象:


在onDraw方法,里面打印Log。


这个方法什么也不动,它每隔0.5秒绘制一次。
和光标的闪烁一样的频率。


如果在这里面
new int[1024*1024] 
移动的时候也会分配,所以这个会让程序非常非常慢。


所以不要再onDraw方法创建对象。


Android 自己的操作系统,正式的绘制的频率是16ms绘制一次。


每一个控件都有自己的绘制频率。


例如EditText 0.5s一次。它用在光标的闪烁。


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


自定义的属性


我们想要加入
lineColor="#0F0" 


这个时候需要我们自己添加:


在values 文件夹,添加一个values 文件叫做attr.xml


btw:
format 的num 可以用来形容orientation 这样的属性


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--自定义属性的声明-->


    <!--name 通常推荐是类名,这样好区别
    可以认为是给某一个对应的空间声明属性
    -->
    <declare-styleable name="NotePadView">
        <!--attr代表定义的属性名称,相当于NotePadView内部支持属性定义-->
            <attr name="lineColor" format="color"/>
        <!--定义一个名称为lineHeight的属性,内容是尺寸-->
        <attr name="lineHeigth" format="dimension"/>
    </declare-styleable>
</resources>




这个时候就可以添加 app:lineColor="#0F0"


通常命名空间的名称是app,是自动生成的,实际上就是xml命名空间;


在main的xml 里面自动导入了:
xmlns:app="http://schemas.android.com/apk/res-auto"


这个时候设置还没有完全的好。


还需要在代码中获取自定义属性;


更新init 如下:
private void init(Context context,AttributeSet set){
            linePaint = new Paint();
        //linePaint.setColor(Color.RED);
        //TODO: 从AttributeSet获取属性配置,来设置横线的属性


        int lineColor = Color.RED;




        if(set!=null) {
            //获取属性集合中的lineColor属性
            //获取自定义属性操作步骤:
            //1.先获取指定的,在attrs.xml中定义的那个属性集合declare-style
            //代表的内容
            //obtainStyledAttributes(); 获取自定义属性,需要AttributeSet才可以




            TypedArray array = context.obtainStyledAttributes(
                    set,//包含了xml 中当前控件的所有属性 android:,app:这两种开头的
                    R.styleable.NotePadView//获取针对NotePadView 的所有属性。
            );
            //获取颜色属性,
            //参数一:index,就是android中自定义属性的索引,
            int color = array.getColor(
                    R.styleable.NotePadView_lineColor,//通过常量已经定义好了index了
                    Color.RED
            );
            linePaint.setColor(color);
            //TypedArray 对象在使用完之后,必须要被回收
            
float dimension = array.getDimension(R.styleable.NotePadView_lineHeigth, 5.5f);
            linePaint.setStrokeWidth(dimension);


array.recycle();
        }
}


------------------------------------------------------------
----------------------------------------------------------------
组合控件,事件分发。
组合方式的自定义View
listView 就是一个组合布局
ViewPager 也是一个组合布局
AutoComplementTextView,Spinner 也都是的。


常见的第三方控件:
1:瀑布流,
2:拨号盘
3:侧滑菜单
4:Path按钮,
5:水纹进度
6:事件滚轮


---------------------------------------------------------------------
----------------------------------------------------------------------
继承ViewGroup 以及ViewGroup 的子类


新建一个java 文件AlphaIndicator


/**
 * 通过组合的形式,实现字模选择的功能
 * 采用组合控件的形式。
 */
public class AlphaIndicator extends LinearLayout {
    public AlphaIndicator(Context context) {
        //super(context); 这个也行,下面的也行
        this(context,null);
    }


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


    private void init(Context context,AttributeSet attrs){
            //TODO:初始化各种内容和各种属性
        //1.添加A-Z 26个字母
        char ch;
        for (int i = 0; i <26; i++) {
            ch =(char)('A'+i);
            TextView  textView = new TextView(context);
            textView.setText(Character.toString(ch));
            //关于layoutParams,通过LayoutParams 可以给一个控件设置相应的布局属性,
            //控件添加到哪一种布局中,就是用这个布局的LayoutParams的对象来设置。
            //例如控件添加到LinearLayout ,那么个给这个空间设置的就是
            //LinearLayout.LayoutParams 对应的就是android:layout_xxx
            LinearLayout.LayoutParams lp = new LayoutParams(
                    //32,//像素单位的实际数值,或者是WRAP_CONTENT,MATCH_PARENT
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    0,//0高度,因为使用权重,让控件填满布局,
                    1//weight 对应layout_weight="1"
            );


            textView.setLayoutParams(lp);//设置布局参数
            //设置textSize 属性
            textView.setTextSize(TypedValue.COMPLEX_UNIT_SP,20);


            //添加进
            addView(textView);
        }


    }
}




<com.kodulf.customerview1.widget.AlphaIndicator
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:orientation="vertical"
        ></com.kodulf.customerview1.widget.AlphaIndicator>




更新完善:
//设置Android:gravity
            textView.setGravity(Gravity.CENTER);
            textView.setTextColor(Color.RED);




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


组合控件里面的事件处理


1:控件的触摸事件分为两种形式:
1)控件自身的onTouchEvent(...),控件自己来实现和处理触摸事件,是一种默认的处理
2) 给空间设置的setOnTouchListener();外部来设置触摸事件的处理【表情】可以覆盖onTouchEvent();
默认的处理;
2:通常对于自定义空间而言,可以采用onTouchEvent,外部将setOnTouchListener 留给外部代码;
3:触摸事件的类型:DOWN,MOVE,UP等,
4:ACTION_DOWN, 如果
//!!!!!如果ACTION_DOWN的状态,返回true 代表当前控件需要继续
                //执行其他状态的监控;如果返回false,或者super调用,
                //都不会再收到触摸事件了;
--------------------------------------------------------------------------------------




    /**
     * 空间自身处理触摸屏幕的事件
     * @param event
     * @return true 代表当前时间处理完成,芙蓉起不要再处理;false,代表芙蓉起还可以继续处理
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //action 代表触摸的操作状态。
        int action = event.getAction();
        String type="";
        switch (action){
            case MotionEvent.ACTION_DOWN:
                //所有的触摸屏幕操作都是从ACTION_DOWN开始的,也就是手指按下开始
                //!!!!!如果ACTION_DOWN的状态,返回true 代表当前控件需要继续
                //执行其他状态的监控;如果返回false,或者super调用,
                //都不会再收到触摸事件了;
                type="DOWN";
                break;
            case MotionEvent.ACTION_MOVE:
                //手指移动
                type="MOVE";
                break;
            case MotionEvent.ACTION_UP:
                type="UP";
                //手指离开屏幕
                break;
        }
        Log.d("151222MY", type);
        return super.onTouchEvent(event);
    }
上面只会显示DOWN
//!!!!!如果ACTION_DOWN的状态,返回true 代表当前控件需要继续
                //执行其他状态的监控;如果返回false,或者super调用,
                //都不会再收到触摸事件了;
--------------------------------------------------------


更新:
/**
     * 空间自身处理触摸屏幕的事件
     * @param event
     * @return true 代表当前时间处理完成,芙蓉起不要再处理;false,代表芙蓉起还可以继续处理
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
---》        boolean ret = false;
        //action 代表触摸的操作状态。
        int action = event.getAction();
        String type="";
        switch (action){
            case MotionEvent.ACTION_DOWN:
                //所有的触摸屏幕操作都是从ACTION_DOWN开始的,也就是手指按下开始
                //!!!!!如果ACTION_DOWN的状态,返回true 代表当前控件需要继续
                //执行其他状态的监控;如果返回false,或者super调用,
                //都不会再收到触摸事件了;
                type="DOWN";
---》                ret=true;
                break;
            case MotionEvent.ACTION_MOVE:
                //手指移动
                type="MOVE";
                break;
            case MotionEvent.ACTION_UP:
                type="UP";
                //手指离开屏幕
                break;
        }
        Log.d("151222MY", type);
        //return super.onTouchEvent(event);
---》        return ret;
    }






------------------------------------------------------------------------------------------
    /**
     * 使用成员变量,进行上一次选中位置的保存,
     * 便面多次选中线通的位置,
     * 因为mover的动作会执行多次。
     */
private int currentPosition = -1;
    /**
     * 空间自身处理触摸屏幕的事件
     * @param event
     * @return true 代表当前时间处理完成,芙蓉起不要再处理;false,代表芙蓉起还可以继续处理
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean ret = false;
        //action 代表触摸的操作状态。
        int action = event.getAction();


        //触摸事件包含x,y
        float ex = event.getX();
        float ey = event.getY();
        //Log.d("151222MY","x:"+ex+" y:"+ey);
        //TODO:事件相对于手机屏幕的x,y如何获取


        String type="";
        switch (action){
            case MotionEvent.ACTION_DOWN:
                //所有的触摸屏幕操作都是从ACTION_DOWN开始的,也就是手指按下开始
                //!!!!!如果ACTION_DOWN的状态,返回true 代表当前控件需要继续
                //执行其他状态的监控;如果返回false,或者super调用,
                //都不会再收到触摸事件了;
                type="DOWN";
                //因为每次按下 是新的触摸事件刘晨给,那么续重位置清空,
                currentPosition=-1;
                ret=true;
                break;
            case MotionEvent.ACTION_MOVE:
                //手指移动
                type="MOVE";
                int count = getChildCount();//获取内部的控件个数,根据空间个数,找到手机放在那个位置上了
                int position =-1;
                for(int i=0;i<count;i++){
                    View view = getChildAt(i);
//                    float viewX = view.getX();
//                    float viewY = view.getY();
                    float viewX = view.getLeft();
                    float viewY = view.getTop();


                    int viewRight = view.getRight();
                    int viewBottom = view.getBottom();


                    if(ex>=viewX&&ex<=viewRight){
                        if(ey>=viewY&&ey<=viewBottom){
                           //当前点中了,这个控件
                            position=i;
                            break;
                        }
                    }
                }


                if(position>-1&&position!=currentPosition){
                   // Toast.makeText(getContext(),"选中:"+position,Toast.LENGTH_SHORT).show();
                    Log.d("151222MY","选中:"+position);
                    currentPosition=position;
                }
                break;
            case MotionEvent.ACTION_UP:
                type="UP";
                //手指离开屏幕
                break;
        }
        //Log.d("151222MY", type);
        //return super.onTouchEvent(event);
        return ret;
    }






-----------------------------------------------------------------
更新:
if(position>-1&&position!=currentPosition){
                    View v = getChildAt(position);
                    String alpha =(String)v.getTag();
                   // Toast.makeText(getContext(),"选中:"+position,Toast.LENGTH_SHORT).show();
                    //Log.d("151222MY","选中:"+position);
                    Log.d("151222MY","选中:"+alpha);
                    currentPosition=position;
                }


更新 添加textView.setTag(ch);到下面的方法中。
private void init(Context context,AttributeSet attrs){
            //TODO:初始化各种内容和各种属性
        //1.添加A-Z 26个字母
        char ch;
        for (int i = 0; i <26; i++) {
            ch =(char)('A'+i);
            TextView  textView = new TextView(context);
            textView.setText(Character.toString(ch));
            textView.setTag(ch);





你可能感兴趣的:(自定义View <0> 继承现有的控件)