安卓面试题之---自定义View

 安卓面试题之---自定义View_第1张图片

##一,自定义View的分类
- 1.继承View或者ViewGroup类,重写onDraw方法,调用invalidate方法重新绘制View(譬如说计数器)

- 2.自定义组合控件,即将几种控件组合起来形成一个新的控件,这个新的组合控件就会整合了原来每一个控件的功能(譬如说新浪微博中ListView第一行上面的状态栏),(继承组合控件布局的跟布局)

- 3.自定义扩展控件,也就是继承现有的控件,在该控件的基础之上添加新的功能。(譬如继承Button,tablayout等)


##二构造方法
    1,自定义view的第一步是写构造方法,构造方法是用来初始化对象的,包括view也是对象。


    2,构造方法在这里一般要写三个甚至四个,这样写的原因:我们在不同的情况下创建View的方式不同,可能需要从xml文件中填充布局,也可能不需要,或者也需要一样style之类的,因此不同情况下,使用的构造可能存在差异。

       因此构造方法也有这么多种类。从API上描述我们一定要有第二个构造方法。(在实际开发中也可以第一个调用第二个,第二个调用第三个构造,确保使用了每一种) 
    3,

    第一个构造:是在java创建视图的时候调用,如果从xml文件中填充,则不会调用这个构造方法;
    第二个构造方法 :用于layout文件实例化,会把xml中的参数通过attrs带入;
    第三个构造方法:这个构造方法是在第二个基础上再传入style的.

 


##三,主要方法
(一)onMeasure()用于测量子控件的宽高
     1.2,MeasureSpec在很大程度上决定了一个View的尺寸规格,

     1.1,模式:exactly, at_most, unspecified
         Exactly是写出具体的dp值,
         at_most一般对应wrap_content,最大值不能超过父控件宽高
         Unspecified,一般在scrollView或者listview中,要多大就多大


     1.3,常见的三个方法
         makeMeasureSpec(int size ,int mode)
         getMode(int measureSpec)
         getSize(int measureSpec)


     1.4,makeMeasureSpec()方法的作用将size 和 mode 打包成一个32位的int值,之所以这样做就是为了减少内存的分配。返回值为打包成的int类型值measureSpec 。 
    
     1.5,getMode 和 getSize 则是根据传入的int 类型值,解包成为 mode 和 size。

     1.6,只处理AT_MOST情况也就是wrap_content,其他情况则沿用系统的测量值即可。
    setMeasuredDimension会设置View宽高的测量值,只有setMeasuredDimension调用之后,才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。
    
     如果我们不处理AT_MOST情况,那么即使设置了wrap_content,最终的效果也和match_parent一样,这是因为这种情况下,view的SpecSize就是父容器测量出来可用的大小。
 


- (二)在onLayout()用于摆放子控件在父控件中的位置,只有ViewGroup才能让子控件显示在自己的什么位置.只会触发,执行一次

    1,getWidth()方法和getMeasureWidth()的值基本相同。

    2,但getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。

    3,另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

    4,我们在ViewGroup中重写onLayout的目的:
    就是设置当前View与其所有的子View,在ViewGroup(或其继承ViewGroup的Layout)父布局当中的位置。
    
    5,childView.getMeasuredWidth();//在onMeasure()方法之后取得View的实际宽、高

    childView.getMeasuredHeight();

 


(三)onDraw() 用于绘制需要的图形

- 1,主要通过canvas,paint,matrix去绘制

- 2,canvas,是一个绘制工具 ,canvas常用的方法有:
    比如drawXXX(画图,画直线等)
    matrixxxx,放大缩小,压缩
    clipxxxx裁剪

    1,drawRect(RectF rect, Paint paint) //绘制区域,参数一为RectF一个区域

    2,
drawPath(Path path, Paint paint) //绘制一个路径,参数一为Path路径对象

    3,
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) //贴图,参数一就是我们常规的Bitmap对象,参数二是源区域(这里是bitmap),参数三是目标区域(应该在canvas的位置和大小),参数四是Paint画刷对象,因为用到了缩放和拉伸的可能,当原始Rect不等于目标Rect时性能将会有大幅损失。

    4,
drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) //画线,参数一起始点的x轴位置,参数二起始点的y轴位置,参数三终点的x轴水平位置,参数四y轴垂直位置,最后一个参数为Paint 画刷对象。

    5,
drawPoint(float x, float y, Paint paint) //画点,参数一水平x轴,参数二垂直y轴,第三个参数为Paint对象。

    6,
drawText(String text, float x, floaty, Paint paint) //渲染文本,Canvas类除了上面的还可以描绘文字,参数一是String类型的文本,参数二x轴,参数三y轴,参数四是Paint对象。

    7,
drawOval(RectF oval, Paint paint)//画椭圆,参数一是扫描区域,参数二为paint对象;

    8,
drawCircle(float cx, float cy, float radius,Paint paint)// 绘制圆,参数一是中心点的x轴,参数二是中心点的y轴,参数三是半径,参数四是paint对象;

    9,
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧,参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧,参数二是起始角(度)在电弧的开始,
    参数三扫描角(度)开始顺时针测量的,参数四是如果这是真的话,包括椭圆中心的电弧,并关闭它,如果它是假这将是一个弧线,参数五是Paint对象;


- 3,paint常用方法
        1.Paint.setStyle(Style style) 设置绘制模式
        2.Paint.
setColor(int color) 设置颜色
        3.Paint.
setStrokeWidth(float width) 设置线条宽度,画笔样式为空心时,设置空心画笔的宽度
        4.Paint.setTextSize(float textSize) 设置文字大小
        5.Paint.
setAntiAlias(boolean aa) 设置抗锯齿开关,

      6,setAlpha(int a)  //设置画笔的透明度[0-255],0是完全透明,255是完全不透明
      7,
setColorFilter(ColorFilter filter)//设置图形重叠时的显示方式,下面来演示一下
      8,
setARGB(int a, int r, int g, int b) //设置画笔颜色,argb形式alpha,red,green,blue每个范围都是[0-255],
      9,
setTextScaleX(float scaleX)  //设置字体的水平方向的缩放因子,默认值为1,大于1时会沿X轴水平放大,小于1时会沿X轴水平缩小
     10,setTypeface(Typeface typeface) //设置字体样式,
     11 ,setFakeBoldText(boolean fakeBoldText) //设置文本粗体
     12 ,setStrikeThruText(boolean strikeThruText) //设置文本的删除线
     13,setUnderlineText(boolean underlineText) //设置文本的下划线
     14,
reset() , 重置Paint 
     15 ,setFlags(int flags)  ,//设置一些标志,比如抗锯齿,下划线等等

      16,setLetterSpacing(float letterSpacing) //设置行的间距,默认值是0,负值行间距会收缩
      17,
setStrokeMiter(float miter) //当style为Stroke或StrokeAndFill时设置连接处的倾斜度,这个值必须大于0,看一下演示结果
     18,setDither(boolean dither) //设置是否抖动,如果不设置感觉就会有一些僵硬的线条,如果设置图像就会看的更柔和一些,

     19,setStrokeCap(Paint.Cap cap)
              设置线冒样式,取值有Cap.ROUND(圆形线冒)、Cap.SQUARE(方形线冒)、Paint.Cap.BUTT(无线冒)
     20,
setStrokeJoin(Paint.Join join)
           设置线段连接处样式,取值有:Join.MITER(结合处为锐角)、Join.Round(结合处为圆弧)、Join.BEVEL(结合处为直线)


- 3,onMeasure()和onLayout()最后都要调用requestLayout()才能让改变生效,onDraw()要调用invalidate()才能让改变生效,postInvalidate()(在子线程调用)才能生效;

 

- 4,其他方法

    1,invalidate() 触发重新绘制,只能在主线程调用
    2,postInvalidate()    直接调用去在子线程更新UI
    3,onAttachedToWindow()//当View附加到窗体的时候调用该方法,
        可以用于注册广播,注册EventBus
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        EventBus.getDefault().register(BaseTabPage.this);
    }


    4,onDetachedFromWindow() //当销毁View的时候,可以用来反注册广播监听,反注册EventBus
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        EventBus.getDefault().unregister(BaseTabPage.this);
    }


    5,onFinishInflate()  是当所有的孩子都解析完后的一个调用
    6,,requestLayout() 会触发measure过程和layout过程


##四,其他
- 1,直接继承自View和ViewGroup的控件,padding是默认无法生效的,需要自己处理.

- 2,直接继承自view的控件,如果不对wrap-content做特殊处理,那么使用wrap-content和使用match-content的效果是一样的.

- 3,requeLayout会触发 onMesure()和 onLayout()重新执行
    比如 ScrollView中有LinearLaout ,LinearLayout里面有纵向排列的ImageView和TextView,那么假如ImageView的长宽发生了变化,而要立即在手机上显示这个变化的话,就可调用 imageView.requestLayout();这样的话ScrollView 会重新执行onMesure()这个方法会确定控件的大小然后在确定出自己的宽高,最后在执行onLayout(),这个方法是对所有的子控件进行定位的。

- 4,在View中,measure()方法是个final类型,也不能被子类调用。但是layout是一个普通方法,onLayout是一个实现了空方法。layout()方法是个final类型,所以不能被子类调用。

 

##五,自定义属性

https://blog.csdn.net/qq_38859786/article/details/80692201

 

##六,自定义组合式控件

1,代码

public class Custome_Time extends RelativeLayout implements View.OnClickListener {

    private TextView mTv_time_start;
    private TextView mTv_time_end;
    private TwoBtnListener.OnLeftListener startListener;
    private TwoBtnListener.OnRightListener endListener;

    public Custome_Time(Context context) {
        super(context);
        init(context);
    }


    public Custome_Time(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public Custome_Time(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measure(widthMeasureSpec,heightMeasureSpec);
    }

    private void init(Context context) {
        View time_view = LayoutInflater.from(context).inflate(R.layout.custome_time, null);
        addView(time_view);
        mTv_time_start = (TextView) time_view.findViewById(R.id.tv_time_start);
        mTv_time_end = (TextView) time_view.findViewById(R.id.tv_time_end);

        mTv_time_start.setOnClickListener(this);
        mTv_time_end.setOnClickListener(this);
    }

    public void setStartTime(String beforeTime){
        mTv_time_start.setText(beforeTime);
    }
    public void setEndTime(String afterTime){
        mTv_time_end.setText(afterTime);
    }



    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv_time_start:
                startListener.onLeftListener();
                break;
            case R.id.tv_time_end:
                endListener.onRightListener(v);
                break;
        }
    }

    public void setOnClickStart(TwoBtnListener.OnLeftListener listener){
        this.startListener = listener;
    }

    public void setOnClickEnd(TwoBtnListener.OnRightListener listener){
        this.endListener = listener;
    }

 

2,布局,采用merge,因为Custome_Time extends LinearLayout,所以根据局就可以省略,直接用merge,减少层级

3,在代码中使用

 






    


        

    

    

    

        
    



 

//TODO 时间选择的逻辑其实应该放在CutomeTime里实现,这样最大化的解耦,有时间再做吧

mCustome_time = (Custome_Time) mHeaderView1.findViewById(R.id.custome_time);
mCustome_time.setOnClickStart(new TwoBtnListener.OnLeftListener() {
    @Override
    public void onLeftListener() {
        AppUtil.hideInputMethod(HappyDeviceRepairCustomerComplaintActivity.this);
        popCustomeComplaintsDate(Constant.SEARCHBEFORDATE);
    }
});

mCustome_time.setOnClickEnd(new TwoBtnListener.OnRightListener() {
    @Override
    public void onRightListener(View view) {
        AppUtil.hideInputMethod(HappyDeviceRepairCustomerComplaintActivity.this);
        popCustomeComplaintsDate(Constant.SEARCHAFTERDATE);

    }
});

 

mCustome_time.setStartTime(getString(R.string.orderform_before));
mCustome_time.setEndTime(getString(R.string.orderform_after));


##优质博客
- 1,hencoder
    https://hencoder.com/ui-1-2/
- 2,Android自定义View(二、深入解析自定义属性)
    https://blog.csdn.net/xmxkf/article/details/51468648#1-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E8%87%AA%E5%AE%9A%E4%B9%89%E5%B1%9E%E6%80%A7
- 3,自定义View学习笔记之详解onMeasure
    https://www.jianshu.com/p/1695988095a5
- 4,1,android自定义View一(基础和原理)
    https://blog.csdn.net/androidxiaogang/article/details/51849136

   5,Android View的工作流程



 

你可能感兴趣的:(自定义view)