美文网首页
自定义控件

自定义控件

作者: 叙忆 | 来源:发表于2017-10-19 11:02 被阅读0次
  1. 自定义属性的声明和获取
  2. 测量onMeasure
  3. 布局onLayout(ViewGroup)
  4. 绘制onDrow
  5. onTouchEvent
  6. onInterceptTouchEvent(ViewGroup)
1. 自定义属性声明和获取
  • 分析需要的自定义属性
  • 在res/values/attrs.xml定义声明
  • 在layout.xml文件中使用
  • 在View的构造方法中获取
     <declare-styleable name="MyActionbar"> 
        <attr name="ma_title" format="string|reference"/>
        <attr name="ma_title_text_color" format="color|reference"/>
        <attr name="ma_right_img_visible" format="boolean"/>
        <attr name="ma_back_img_visible" format="boolean"/>
        <attr name="ma_right_img" format="reference"/>
        <attr name="ma_back_img" format="reference"/>
        <attr name="ma_second_img" format="reference"/>
        <attr name="ma_center_img" format="reference"/>
        <attr name="ma_right_title" format="boolean"/>
        <attr name="ma_center_img_visible" format="boolean"/>
        <attr name="ma_bg" format="color|reference"/>
    </declare-styleable>
  public MyActionbar(final Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        LayoutInflater.from(context).inflate(R.layout.custom_view_actionbar, this);
        mBackImg = (ImageView) findViewById(R.id.bar_back_img);
      
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.MyActionbar);

        boolean rightTitleVisible = a.getBoolean(R.styleable.MyActionbar_ma_right_title, false);
        int backImgId = a.getResourceId(R.styleable.MyActionbar_ma_back_img, 0);
        if (backImgId != 0) {
            mBackImg.setVisibility(VISIBLE);
            mBackImg.setImageResource(backImgId);
        }

        mBackImg.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
            }
        });
        a.recycle();
    }

2. 测量onMeasure

主要由测量模式和测量数值决定
ViewGroup就是测量子view的宽和高

  1. EXACTLY,AT_MOST,UNSPECIFIED
  2. MeasureSpec
  3. setMeasuredDimension
  4. requestLayout

说明:

  • EXACTLY明确的值,例如100dp的值,match_parent
  • AT_MOST至多不能超过某个值,例如wrap_content,说明本身控件是由自身内容决定,最大不能超过父控件
  • UNSPECIFIED没有限制,例如scrollview,listview
  • 测量模式和测量数值由辅助MeasureSpec决定
  • 测量是由requestLayout重新设置
    例如TextView.setText(内容),在方法里面调用requesLayout
    private int measureHeight(int heightMeasureSpec) {
        int result = 0;
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);

        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = getHeight() + getPaddingBottom() + getPaddingTop();//计算本身高度

            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        return result;
    }
3. 布局onLayout(只有viewGroup才有)
  1. 决定子view在viewgroup中的位置(如果是view不用考虑)
  2. 尽可能将onMeasure中的一些操作放到这个方法(耗时操作可以放这里)
  3. requestLayout()
4. 绘制onDrow
  1. 绘制内容区域,背景不算在内
  2. invalidate(UI线程中调用),postInvalidate(子线程调用);界面变化
  3. Canvas.drawXXX
  4. translate,rotate,scale,skew
  5. save,restore
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //使用Canvas相关api绘制
    }
5. onTouchEvent
  1. ACTION_DOWN,ACTION_MOVE,ACTION_UP
  2. ACTION_POINTER_DOWN,ACTION_POINTER_UP
  3. parent.requestDisallow
    ——InterceptTouchEvent(true)
  4. VelocityTracker
6. onInterceptTouchEvent(ViewGroup的方法)

返回true,表示拦截子控件的事件

7. 保存状态

例如progressBar保存进度

  • onSaveInstanceState,onRestoreInstanceState
    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable("instance", super.onSaveInstanceState());
        bundle.putFloat("alpha", mAlpha);
        return bundle;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {

        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            mAlpha = bundle.getFloat("alpha");
            super.onRestoreInstanceState(bundle.getParcelable("instance"));
            return;
        }
        super.onRestoreInstanceState(state);
    }

案例

  • generateLayoutParams
    父容器生成子 view 的布局LayoutParams;
    如果一个View想要被添加到这个容器中,这个view可以调用此方法生成和容器类匹配的布局
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        View childView = getChildAt(0);
        //对应 generateLayoutParams() 返回的Layout
        MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
        //注意 宽度不是获取 params.width 
        int width = childView.getMeasuredWidth() + params.leftMargin + params.rightMargin;
        int height = childView.getMeasuredHeight() + params.topMargin + params.bottomMargin;
        childView.layout(0, 0, width, height);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int resultWidth = 0;
        int resultHeight = 0;
        //获取长度
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        //获取模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);

        if (widthMode == MeasureSpec.EXACTLY) {
            resultWidth = widthSize;
        } else {
            //测量所有子view的宽度
        }

        //重新设置高度
        setMeasuredDimension(resultWidth, resultHeight);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

相关文章

网友评论

      本文标题:自定义控件

      本文链接:https://www.haomeiwen.com/subject/tmzyuxtx.html