Contents
  1. 1. 2016/1/14 10:24:56
    1. 1.1. 自定义View的基础知识
  2. 2. 2016/1/15 0:32:26
    1. 2.1. 实现自定义控件有3种方式:
  3. 3. 2015-10-05 09:51:46
    1. 3.1. 组合View
      1. 3.1.0.1. 为自定义View添加自定义属性
  • 4. 2015-10-08 18:20:56
    1. 4.1. 实战开发帮你教项目中的意见反馈功能
  • 5. 2015-10-05 09:51:07
    1. 5.0.1. 完全自定义View(进阶)
  • 5.1. 自定义View的layout文件根标签为match_parent导致CustomView设置warp_content时,会填满整个布局文件
  • 6. 2015-10-05 09:51:16
    1. 6.0.1. 自定义View — 重写onMeasure
  • 7. 2015-10-05 10:02:11
    1. 7.0.1. 自定义监听器
  • 8. 2016/1/15 0:37:22
    1. 8.1. 实际开发 — 分割线
  • 9. 2016/1/11 11:20:59
    1. 9.1. 为RecycleView重写SwipeRefreshLayout
  • 10. 2016/1/21 15:56:49
    1. 10.1. 实现请求View RequestView
  • 2016/1/14 10:24:56

    自定义View的基础知识

    • getWidth()、getHeight()指的是xml文件中该View指定的width,height。
    • onMeasure、onLayout、onDraw先后执行顺序。
    • 获取屏幕宽高

      WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
      width = wm.getDefaultDisplay().getWidth();
      height = wm.getDefaultDisplay().getHeight();
      

    2016/1/15 0:32:26

    实现自定义控件有3种方式:

    1. 继承一个控件 (继承已有控件)
    2. 继承一个布局(继承已有布局)
    3. 组合View (布局根标签应该设置成warp_content,保证各个控件的高宽属性正常。)

    2015-10-05 09:51:46

    组合View

    基本思路:

    1. xml布局文件添加已有的控件,例如(ImageView跟Button)。
    2. 创建java文件继承包含这些控件的布局,例如(RelativeLayout)。
    3. 重写其构造方法(加载xml布局文件,利用通过LayoutInflate)。
    4. 在其构造方法里获取自定义属性。(可选,在values文件夹创建一个attrs的xml文件)
    为自定义View添加自定义属性
    • 在attrs文件中创建declare-styleable标签,其name属性是控件名字(不需要加包名)
      在上述标签下 创建attr标签,name属性是属性名、format指明改属性的格式

      <attr name="src"></attr> // name默认不加前缀 则调用方法为app:src
      <attr name="text"></attr>
      

    属性里的值可以通过enum标签来实现
    reference 表示引用,参考某一资源ID
    string 表示字符串
    color 表示颜色值
    dimension 表示尺寸值
    boolean 表示布尔值
    integer 表示整型值
    float 表示浮点值
    fraction 表示百分数
    enum 表示枚举值
    flag 表示位运算

    定义的属性需赋值给原控件的属性

    1. 利用TypedArray 获取所有属性
    //通过context的obtainStyledAttributes(attrs,R.styleable.控件名)方法得到一个TypedArray对象,参数分别为attrs、控件id
    TypedArray array=context.obtainStyledAttributes(attrs,R.styleable.自定义控件名);
    
    1. 利用typedArray对象设置控件默认值(R.styleable.值)

      CharSequence text=  array.getText(R.styleable.自定义控件名_android_text);
      Drawable drawable= array.getDrawable(R.styleable.自定义控件名n_android_src);
      
    2. 通过原有控件的方法设置这些属性即可。

      editText.setHint(text);
      
    3. TypedArray对象调用recycle方法

      array.recycle();
      
    4. 在使用该控件的布局文件里设置命名空间

      xmlns:eri="http://schemas.android.com/apk/res/包名"  //包名指整个项目的包名
      
    1. 通过该命名空间调用属性 如 eri:属性名

      eri:text="Erintrus"
      

    2015-10-08 18:20:56

    实战开发帮你教项目中的意见反馈功能

    1.写一个布局包括一个EditText 和TextView
    2.写attrs文件,添加一个hint属性,用于设置提示
    3.重写java 文件,获取到属性的值后,赋值给原控件
    如下:

    //获取自己定义的属性,然后赋值给原控件的属性。从而实现自定义属性的功能

    TypedArray array=context.obtainStyledAttributes(attrs,R.styleable.EtextWithTv);
    

    //得到自定义属性的值

    CharSequence text=  array.getText(R.styleable.EtextWithTv_hint);
    array.recycle();  
    

    //调用EditText原有属性

    editText.setHint(text);
    

    4.写一个getText方法,用于获取EditText的值(使用该控件时候,可以调用该方法)

    public String getText()
    {
    return editText.getText().toString();
    }
    

    2015-10-05 09:51:07

    完全自定义View(进阶)

    1.绘图,通过重写onDraw方法控制View在屏幕上的渲染效果

    2.交互,通过重写onTouchEvent方法或者使用手势来控制用户的交互

    3.测量,通过重写onMeasure方法来对控件进行测量

    4.属性,可以通过xml自定义控件的属性,然后通过TypedArray来进行使用

    5.状态的保存,为了避免配置改变时丢失View状态,通过重写onSaveInstanceState,onRestoreInstanceState方法来保存和恢复状态。

    自定义View的layout文件根标签为match_parent导致CustomView设置warp_content时,会填满整个布局文件

    2015-10-05 09:51:16

    自定义View — 重写onMeasure

    • 判断是否需要重写OnMeasure (下述需求判断是否重写)

    (下述无效,待更新)

    - 不重写onMeasure: (弊端: view为warp、match时,都会填满父容器)
    
    1. 父容器为warp_content或match_parent时,view的宽高不是固定值,则view填满父容器。
    2. 父容器为warp_content或match_parent时,view的宽高是固定值,则view大小取决固定值。
    3. 父容器为固定值时(view的最大显示值),view的宽高必须小于等于其固定值。
    4. 父容器为固定值时(view的最大显示值),view的宽高不是固定值,则view填满父容器。
    
    
        特点: view的宽高为warp_content时,view填满父容器。
    

    (当View为warp_content时,想要设置一个固定值,就应该重写)

    - 重写onMeasure(可实现:view为warp时,显示默认值,而不是填满父容器)
    
    1. 可以给View设定默认值,当view宽高为warp_content时,显示默认值。
    2. 父容器为warp_content或match_parent时,view的宽高为warp_content,则view显示默认值。
    3. 父容器为warp_content或match_parent时,view的宽高为match_parent,则view填满控件。
    4. 父容器为固定值时(view的最大显示值),view的宽高必须小于等于其固定值。
    
    
        特点: view的宽高为warp_content时,view大小为默认值。
    
    
        - 重写onMeasure的代码
    
                @Override
                protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                    super.onMeasure(widthMeasureSpec,heightMeasureSpec);
                    int width = getSize(DEFAULT_WIDTH,widthMeasureSpec);
                    int height = getSize(DEFAULT_HEIGHT,heightMeasureSpec);
                    setMeasuredDimension(width, height);
                }
    
    
                public int getSize(int defaultSize, int measureSpec) {
                    int result = defaultSize;
                    int specMode = MeasureSpec.getMode(measureSpec);
                    int specSize = MeasureSpec.getSize(measureSpec);//获取测量值,即使用时传入的值。
                    if (specMode == MeasureSpec.EXACTLY) {
                        result = specSize;    //当设置其宽高为固定值或者match时,显示为固定值或者填满屏幕。
                    } else {
                        result = Math.min(defaultSize, specSize); //当设置其宽高为warp时,显示默认值。
                    }
                    return result;
                }
    

    参考

    1.ViewGroup(View的父容器)提供两个参数给View进行设置测量,并且根据测量值跟自己提供的尺寸大小来确定View的大小。

    2.View的大小由Size和Mode决定,相关类是MeasureSpec。Size、Mode封装在resolveSizeAndState方法里面。

    3.resolveSizeAndState方法的参数(View的宽或高、父容器提供的尺寸值、掩码默认0)

    4.如何重写OnMeasure

    //获取View的宽度
    int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
    //讲View的宽度跟ViewGroup给的值进行测量,得出一个整型.
    int w = resolveSizeAndState(minw, widthMeasureSpec, 1);
    
    //同理View的高度
    int minh = MeasureSpec.getSize(w) + getPaddingBottom() + getPaddingTop();
    int h = resolveSizeAndState(MeasureSpec.getSize(w), heightMeasureSpec,0);
    

    参考

    2015-10-05 10:02:11

    自定义监听器

    1.写一个接口,并且添加抽象方法

    public interface onStateChangedListener {
    public void stateOn();
    
        public void stateOff();
    }
    

    2.在自定义控件中创建接口引用,并且生成set方法,用于外面调用

    public class SwtBtnWithTv extends LinearLayout {
    
    private onStateChangedListener listener;
        public void setListener(onStateChangedListener listener) {
    this.listener = listener;
    }
    

    3.自定义控件实现原有的接口,在这里通过引用调用抽象方法

    switchButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    
    if (isChecked) {
    
    listener.stateOn();
    } else {
    listener.stateOff();
    }
    
        }
    });
    

    4.在Main中调用set方法来实现该功能

    swtBtnWithTv.setListener(new onStateChangedListener() {
    @Override
    public void stateOn() {
            Toast.makeText(getActivity(), "成功", 0).show();
    }
    
    @Override
    public void stateOff() {
            Toast.makeText(getActivity(), "失败", 0).show();
    }
    });
    

    总结:
    Main外面写方法的实现 第5
    自定义控件内部写在哪里调用 第3.4

    2016/1/15 0:37:22

    实际开发 — 分割线

    思路:

    1. 继承View。
    2. 在attrs.xml文件添加两个属性分别是方向、颜色。
    3. 构造方法获取属性、获取屏幕宽高、初始化默认宽高值。
    4. 重写onMeasure、设置默认值。(参考上述)
    5. 重写onDraw。
    * Created by Erintrus on 2015/10/9.
    * Email: Erintrus@126.com
    * 两点确定一条线
    * 获取屏幕高、宽
    * 当要改变wrap_content时,需要设置默认值
    
    
           public class CustomDivider extends View {
    
           private int orientation = 0;
           private static final int HORIZONTAL = LinearLayout.HORIZONTAL;
           private static final int VERTICAL = LinearLayout.VERTICAL;
    
           private static final int DEFAULT_COLOR = R.color.common_divider_gray; //默认红色的线
           private Paint paint;
           private static final int DEFAULT_LENGTH = 3;
           private int length;
           private int color;
           private int width;
           private int height;
    
    
           public CustomDivider(Context context) {
               this(context, null);
           }
    
           public CustomDivider(Context context, AttributeSet attrs) {
               this(context, attrs, 0);
           }
    
           public CustomDivider(Context context, AttributeSet attrs, int defStyleAttr) {
               super(context, attrs, defStyleAttr);
               WindowManager wm = (WindowManager) getContext()
                       .getSystemService(Context.WINDOW_SERVICE);
               width = wm.getDefaultDisplay().getWidth();
               height = wm.getDefaultDisplay().getHeight();
               TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomDivider);
               orientation = array.getInteger(R.styleable.CustomDivider_dividerOrientation, HORIZONTAL);
               color = array.getColor(R.styleable.CustomDivider_dividerColor, getResources().getColor(DEFAULT_COLOR));
               if (orientation == HORIZONTAL) {
                   length = width;
               } else {
                   length = height;
               }
               array.recycle();
               paint = new Paint();
               paint.setAntiAlias(true);
               paint.setColor(color);
               paint.setStrokeWidth(5);
               paint.setStyle(Paint.Style.FILL_AND_STROKE);
           }
    
           @Override
           protected void onDraw(Canvas canvas) {
               super.onDraw(canvas);
    
               if (orientation == HORIZONTAL) {
                   canvas.drawLine(DEFAULT_LENGTH, DEFAULT_LENGTH, length, DEFAULT_LENGTH, paint);
               } else {
                   canvas.drawLine(DEFAULT_LENGTH, DEFAULT_LENGTH, DEFAULT_LENGTH, length, paint);
               }
           }
    
           //必须要重写这段代码,使其warp_content可以为某个值
           @Override
           protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
               super.onMeasure(widthMeasureSpec, heightMeasureSpec);
               int w;
               int h;
               if (orientation == HORIZONTAL) {
                   w = getSize(width, widthMeasureSpec);
                   h = DEFAULT_LENGTH;
               } else {
                   w = DEFAULT_LENGTH;
                   h = getSize(height, heightMeasureSpec);
               }
               setMeasuredDimension(w, h);
           }
    
           public int getSize(int defaultSize, int measureSpec) {
               int result = defaultSize;
               int specSize = MeasureSpec.getSize(measureSpec);
               int specMode = MeasureSpec.getMode(measureSpec);
               if (specMode == MeasureSpec.EXACTLY) {
                   result = specSize;
               } else {
                   result = Math.min(defaultSize, specSize);
               }
               return result;
           }
    
    
       }
    

    2016/1/11 11:20:59

    为RecycleView重写SwipeRefreshLayout

    SwipeRefreshLayout

    2016/1/21 15:56:49

    实现请求View RequestView

    作用:

    1. 请求的时候显示进度条加载。
    2. 请求失败时显示失败页面,点击页面可以重新请求。(显示失败的时候才可以点击)

    实现:

    1. 继承RelativeLayout

    2. 布局中使用谷歌的ProgressBar、ImageView、TextView等控件。

    3. 自定义pgbType属性,用于决定ProgressBar的显示类型,并在构造函数中获取。

    4. 由于请求时,不能点击下层的按钮。所以需要设置布局文件clickable为false

    Contents
    1. 1. 2016/1/14 10:24:56
      1. 1.1. 自定义View的基础知识
    2. 2. 2016/1/15 0:32:26
      1. 2.1. 实现自定义控件有3种方式:
    3. 3. 2015-10-05 09:51:46
      1. 3.1. 组合View
        1. 3.1.0.1. 为自定义View添加自定义属性
  • 4. 2015-10-08 18:20:56
    1. 4.1. 实战开发帮你教项目中的意见反馈功能
  • 5. 2015-10-05 09:51:07
    1. 5.0.1. 完全自定义View(进阶)
  • 5.1. 自定义View的layout文件根标签为match_parent导致CustomView设置warp_content时,会填满整个布局文件
  • 6. 2015-10-05 09:51:16
    1. 6.0.1. 自定义View — 重写onMeasure
  • 7. 2015-10-05 10:02:11
    1. 7.0.1. 自定义监听器
  • 8. 2016/1/15 0:37:22
    1. 8.1. 实际开发 — 分割线
  • 9. 2016/1/11 11:20:59
    1. 9.1. 为RecycleView重写SwipeRefreshLayout
  • 10. 2016/1/21 15:56:49
    1. 10.1. 实现请求View RequestView