(4.1.37.4)组合式自定义View

一、前提知识

Linearlayout.LayoutParams params = new LayoutParams(...);
SimapleBanner banner = new SimapleBanner(context);
banner.setLayoutParams(params);
linear.add(banner)

0.1 LayoutInflater的inflate加载布局

LayoutInflater作用是将layout的xml布局文件实例化为View类对象。
获取LayoutInflater的方法有如下三种:

LayoutInflater inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.main, null);

LayoutInflater inflater = LayoutInflater.from(context); (该方法实质就是第一种方法,可参考源代码)
View layout = inflater.inflate(R.layout.main, null);

LayoutInflater inflater = getLayoutInflater();(在Activity中可以使用,实际上是View子类下window的一个函数)
View layout = inflater.inflate(R.layout.main, null);

具体的调用:


public View inflate(XmlPullParser parser, ViewGroup root) {
        return inflate(parser, root, root != null);
}

public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
}

boolean attachToRoot,是否将目标xml加载到指定root中

  • false; 不加载;将这个root对象的LayoutParams属性附加到resource对象的根布局对象上,并返回目标xml本身
  • true;加载
    inflate方法在第二个参数root不为空时,返回的View就是root,而当root为空时,返回的才是加载的layout的根节点
    • 如果提供root(不传null)时,返回值其实就是这个root,这个方法就是把xml解析成view之后挂载这个root下。
    • 如果传null(不提供root),返回值也是View,它就是xml布局里面的根节点

0.2 ViewGroup#addView()函数会生成默认父布局属性

   public void addView(View child, int index) {
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = generateDefaultLayoutParams();
            if (params == null) {
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);
    }

子控件的布局属性类型应该由父布局决定

我们向一个线性布局加入子控件时:

//步骤1:生成线性布局的布局参数
Linearlayout.LayoutParams params = new LayoutParams(...);
//步骤2:生成子控件
SimapleBanner banner = new SimapleBanner(context);
//步骤3:为子控件设置父布局的布局参数
banner.setLayoutParams(params);
//步骤4:父布局将子控件加入自己的布局树中
linear.addView(banner)

可以看到步骤3:为子控件设置父布局的布局参数,如果不设置,那么ViewGroup#addView的时候会加入默认的布局属性

至于为什么,子控件的布局属性是父布局类型决定的,是因为 具体的measure,layout阶段都是父布局获取了子控件的布局属性,做相关操作的,具体可查看(4.1.37.1)深入理解setContentView过程和View绘制过程

模板式写法

定义支持的xml配置属性:

    <declare-styleable name="DiyWidget">
        <attr name="diy_topDividerShow" format="boolean"/>
        <attr name="diy_topDividerIndent" format="boolean"/>
        <attr name="diy_topDividerIndentValue" format="dimension"/>
        <attr name="diy_bottomDividerShow" format="boolean"/>
        <attr name="diy_bottomDividerIndent" format="boolean"/>
        <attr name="diy_bottomDividerIndentValue" format="dimension"/>
        <attr name="diy_contentPaddingleft" format="dimension"/>
        <attr name="diy_contentPaddingRight" format="dimension"/>
        <attr name="diy_contentPaddingTop" format="dimension"/>
        <attr name="diy_contentPaddingBottom" format="dimension"/>
        <attr name="diy_focusedWhenTouched" format="boolean"/>
    </declare-styleable>

定义自己的组合布局:

<!-- 根布局为LinearLayout,与Java的继承类型一致 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/ll_root">
    <include
        android:id="@+id/dw_top_divider"
        layout="@layout/view_divider"
        android:visibility="gone"/>

    <include
        android:id="@+id/dw_bottom_divider"
        layout="@layout/view_divider"
        android:visibility="gone"/>
</LinearLayout>

定义自己的继承类:

public abstract class DiyWidget extends LinearLayout{

    private boolean initCalled;
    protected Context context;

    private LinearLayout llRoot;
    private View topDivider;
    private View bottomDivider;
    private View content;

    /**
    *以下为支持的配置参数
    */
    private int dividerStrokeWidth;

    private boolean topDividerShow;
    private boolean bottomDividerShow;
    private boolean topDividerIndent;
    private boolean bottomDividerIndent;
    private int topDividerIndentValue;
    private int bottomDividerIndentValue;

    protected int contentPaddingLeft;
    protected int contentPaddingRight;
    protected int contentPaddingTop;
    protected int contentPaddingBottom;

    private boolean focusedWhenTouch;
    private boolean initialFocusedWhenTouched;

  public DiyWidget(Context context) {
        super(context);
        if(!initCalled){
            initCalled = true;
            init(context, null);
        }
    }

    public DiyWidget(Context context, AttributeSet attrs) {
        super(context, attrs);
        if(!initCalled){
            initCalled = true;
            init(context, attrs);
        }
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public DiyWidget(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        if(!initCalled){
            initCalled = true;
            init(context, attrs);
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public DiyWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        if(!initCalled){
            initCalled = true;
            init(context, attrs);
        }
    }

    private void init(Context context, AttributeSet attrs){
        this.context = context;
        onPreInit();
        initMembers();
        assignDefaultXml();//为上述相关配置参数,设定默认值
        assignXml(attrs);//传入attrs对象,获取在xml文件配置的相关配置参数

        //将目标xml加载到当前布局中,并配置默认布局属性
        //实例化根布局存储
        LayoutInflater inflater = LayoutInflater.from(context);
        llRoot = (LinearLayout) inflater.inflate(R.layout.view_diy_widget, this).findViewById(R.id.ll_root);

        int layout = getLayout();//返回“自己实质需要的组合View的布局”
        content = inflater.inflate(layout, llRoot, false);
        LinearLayout.LayoutParams lllp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);
        lllp.weight = 1;
        initDividers();
        llRoot.addView(content, 1, lllp);//将其加入到当前布局中

        initLayout(content);//findViewById实例化“自己实质需要的组合View的布局”其中的view
        xmlTakeEffects();//根据配置参数作出对应响应
    }
    //。。。省略一堆


    protected void initDividers(){
        topDivider  = llRoot.findViewById(R.id.dw_top_divider);
        bottomDivider = llRoot.findViewById(R.id.dw_bottom_divider);
    }

    protected void onPreInit(){

    }

    protected void initMembers(){

    }

    protected void assignDefaultXml(){
        Resources res = getResources();
        dividerStrokeWidth = res.getDimensionPixelSize(R.dimen.public_height_line);
        topDividerIndentValue = bottomDividerIndentValue = res.getDimensionPixelSize(R.dimen.diy_widget_divider_indent_default);
    }

    protected void assignXml(AttributeSet attrs){
        if(attrs != null){
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DiyWidget);
            if(typedArray != null){
                topDividerShow = typedArray.getBoolean(R.styleable.DiyWidget_diy_topDividerShow, topDividerShow);
                topDividerIndent = typedArray.getBoolean(R.styleable.DiyWidget_diy_topDividerIndent, topDividerIndent);
                topDividerIndentValue = typedArray.getDimensionPixelSize(R.styleable.DiyWidget_diy_topDividerIndentValue, topDividerIndentValue);

                bottomDividerShow = typedArray.getBoolean(R.styleable.DiyWidget_diy_bottomDividerShow, bottomDividerShow);
                bottomDividerIndent = typedArray.getBoolean(R.styleable.DiyWidget_diy_bottomDividerIndent, bottomDividerIndent);
                bottomDividerIndentValue = typedArray.getDimensionPixelSize(R.styleable.DiyWidget_diy_bottomDividerIndentValue, bottomDividerIndentValue);

                contentPaddingLeft = typedArray.getDimensionPixelSize(R.styleable.DiyWidget_diy_contentPaddingleft, contentPaddingLeft);
                contentPaddingRight = typedArray.getDimensionPixelSize(R.styleable.DiyWidget_diy_contentPaddingRight, contentPaddingRight);
                contentPaddingTop = typedArray.getDimensionPixelSize(R.styleable.DiyWidget_diy_contentPaddingTop, contentPaddingTop);
                contentPaddingBottom = typedArray.getDimensionPixelSize(R.styleable.DiyWidget_diy_contentPaddingBottom, contentPaddingBottom);

                initialFocusedWhenTouched = typedArray.getBoolean(R.styleable.DiyWidget_diy_focusedWhenTouched, initialFocusedWhenTouched);
                typedArray.recycle();
            }

        }
    }

    protected void initLayout(View layout){

    }

    protected void xmlTakeEffects(){
        showTopDivider(topDividerShow);
        showBottomDivider(bottomDividerShow);
        setTopDividerIndent(topDividerIndent);
        setBottomDividerIndent(bottomDividerIndent);
//        setContentHorizontalPadding(contentPaddingLeft, contentPaddingRight);
        setContentPadding(contentPaddingLeft, contentPaddingTop, contentPaddingRight, contentPaddingBottom);
        if(initialFocusedWhenTouched){
            setFocusedWhenTouched(true);
        }
    }



    protected abstract int getLayout();



    public void setContentHorizontalPadding(int paddingLeft, int paddingRight){
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) content.getLayoutParams();
        if(lp == null){
            lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            lp.weight = 1;
        }
        lp.leftMargin = paddingLeft;
        lp.rightMargin = paddingRight;
        content.setLayoutParams(lp);
    }

    public void setContentVerticlePadding(int paddingTop, int paddingBottom){
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) content.getLayoutParams();
        if(lp == null){
            lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            lp.weight = 1;
        }
        lp.topMargin = paddingTop;
        lp.bottomMargin = paddingBottom;
        content.setLayoutParams(lp);
    }

    public void setContentPadding(int paddingLeft, int paddingTop, int paddingRight, int paddingBottom){
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) content.getLayoutParams();
        if(lp == null){
            lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            lp.weight = 1;
        }
        lp.leftMargin = paddingLeft;
        lp.topMargin = paddingTop;
        lp.rightMargin = paddingRight;
        lp.bottomMargin = paddingBottom;
        content.setLayoutParams(lp);
    }

    public void setContentPaddingRight(int inPx){
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) content.getLayoutParams();
        if(lp == null){
            lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            lp.weight = 1;
        }
        lp.rightMargin = inPx;
        content.setLayoutParams(lp);
    }

    public int getContentPaddingLeft(){
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) content.getLayoutParams();
        if(lp != null){
            return lp.leftMargin;
        }
        return 0;
    }

    public int getContentPaddingRight(){
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) content.getLayoutParams();
        if(lp != null){
            return lp.rightMargin;
        }
        return 0;
    }

    public void showTopDivider(boolean show){
        topDivider.setVisibility(show ? View.VISIBLE : View.GONE);
    }

    public void setTopDividerIndentValue(int indentValue){
        if(indentValue == topDividerIndentValue){
            return;
        }
        topDividerIndentValue = indentValue;
        setTopDividerIndent(true);
    }

    public void setTopDividerIndent(boolean indent){
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) topDivider.getLayoutParams();
        if(lp == null){
            lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, dividerStrokeWidth);
        }
        if(indent) {
            lp.leftMargin = topDividerIndentValue;
        }else {
            lp.leftMargin = 0;
        }
        topDivider.setLayoutParams(lp);
    }

    public void showBottomDivider(boolean show){
        bottomDivider.setVisibility(show ? View.VISIBLE : View.GONE);
    }

    public void setBottomDividerIndentValue(int indentValue){
        if(indentValue == bottomDividerIndentValue){
            return;
        }
        bottomDividerIndentValue = indentValue;
        setBottomDividerIndent(true);
    }

    public void setBottomDividerIndent(boolean indent){
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) bottomDivider.getLayoutParams();
        if(lp == null){
            lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, dividerStrokeWidth);
        }
        if(indent) {
            lp.leftMargin = bottomDividerIndentValue;
        }else {
            lp.leftMargin = 0;
        }
        bottomDivider.setLayoutParams(lp);
    }

    protected Size getScreenDimen(){
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        if(displayMetrics != null){
            return new Size(displayMetrics.widthPixels, displayMetrics.heightPixels);
        }
        return null;
    }

    public int getDividerStrokeWidth(){
        return dividerStrokeWidth;
    }

    public void setFocusedWhenTouched(boolean focusedWhenTouch){
        this.focusedWhenTouch = focusedWhenTouch;
        setFocusable(focusedWhenTouch);
        setFocusableInTouchMode(focusedWhenTouch);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(focusedWhenTouch){
            if(!requestFocus()) {
                requestFocusFromTouch();
            }
        }
        return super.dispatchTouchEvent(ev);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值