Skip to content

Latest commit

 

History

History
287 lines (237 loc) · 8 KB

File metadata and controls

287 lines (237 loc) · 8 KB

自定义View属性完整实现指南

在Android中为自定义View添加自定义属性可以让你的组件更加灵活和可配置。以下是实现自定义属性的完整步骤和最佳实践。

一、基本实现步骤

1. 定义自定义属性(attrs.xml)

在res/values/目录下创建或修改attrs.xml文件:

<resources>
    <declare-styleable name="CustomTextView">
        <!-- 基本属性类型 -->
        <attr name="customText" format="string"/>
        <attr name="customTextColor" format="color"/>
        <attr name="customTextSize" format="dimension"/>
        <attr name="customEnable" format="boolean"/>
        <attr name="customCount" format="integer"/>
        <attr name="customFloat" format="float"/>
        
        <!-- 枚举类型 -->
        <attr name="customMode">
            <enum name="normal" value="0"/>
            <enum name="bold" value="1"/>
            <enum name="italic" value="2"/>
        </attr>
        
        <!-- 标志位类型 -->
        <attr name="customFlags">
            <flag name="flagA" value="0x01"/>
            <flag name="flagB" value="0x02"/>
        </attr>
        
        <!-- 引用已有属性 -->
        <attr name="android:text"/>  <!-- 重用系统属性 -->
    </declare-styleable>
</resources>

2. 在自定义View中读取属性

public class CustomTextView extends View {
    private String mCustomText;
    private int mCustomTextColor;
    private float mCustomTextSize;
    private boolean mCustomEnable;
    private int mCustomMode;
    private int mCustomFlags;

    public CustomTextView(Context context) {
        this(context, null);
    }

    public CustomTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        
        // 获取自定义属性
        TypedArray a = context.obtainStyledAttributes(
                attrs, 
                R.styleable.CustomTextView,
                defStyleAttr, 
                0); // defStyleRes
        
        try {
            mCustomText = a.getString(R.styleable.CustomTextView_customText);
            mCustomTextColor = a.getColor(
                    R.styleable.CustomTextView_customTextColor, 
                    Color.BLACK); // 默认值
            mCustomTextSize = a.getDimension(
                    R.styleable.CustomTextView_customTextSize, 
                    16f); // 默认16sp
            mCustomEnable = a.getBoolean(
                    R.styleable.CustomTextView_customEnable, 
                    true);
            mCustomMode = a.getInt(
                    R.styleable.CustomTextView_customMode, 
                    0); // 默认normal
            mCustomFlags = a.getInt(
                    R.styleable.CustomTextView_customFlags, 
                    0);
            
            // 读取系统属性
            String text = a.getString(R.styleable.CustomTextView_android_text);
        } finally {
            a.recycle(); // 必须回收TypedArray
        }
    }
}

3. 在布局中使用自定义属性

<com.example.myapp.view.CustomTextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="默认文本"
    app:customText="自定义文本"
    app:customTextColor="#FF5722"
    app:customTextSize="20sp"
    app:customEnable="true"
    app:customMode="bold"
    app:customFlags="flagA|flagB"/>

注意:需要添加命名空间:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    ...>

二、高级用法

1. 样式继承与默认值

public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    
    // 先读取默认样式
    TypedArray a = context.obtainStyledAttributes(
            attrs, 
            R.styleable.CustomTextView,
            defStyleAttr, 
            R.style.DefaultCustomTextViewStyle); // 默认样式
    
    // ...
}

在res/values/styles.xml中定义默认样式:

<style name="DefaultCustomTextViewStyle">
    <item name="customTextColor">#3F51B5</item>
    <item name="customTextSize">18sp</item>
    <item name="customMode">bold</item>
</style>

2. 属性值变化监听

public class CustomTextView extends View {
    // ...
    
    public void setCustomTextColor(int color) {
        if (mCustomTextColor != color) {
            mCustomTextColor = color;
            invalidate(); // 触发重绘
            requestLayout(); // 如果需要重新测量
        }
    }
    
    // 为所有自定义属性提供setter方法...
}

3. 自定义ViewGroup属性

<declare-styleable name="CustomLayout">
    <attr name="layout_maxChildren" format="integer"/>
    <attr name="layout_orientation">
        <enum name="horizontal" value="0"/>
        <enum name="vertical" value="1"/>
    </attr>
    
    <!-- 子View的布局属性 -->
    <attr name="layout_customGravity">
        <flag name="left" value="0x01"/>
        <flag name="right" value="0x02"/>
        <flag name="center_h" value="0x04"/>
    </attr>
</declare-styleable>

在子View中读取布局属性:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        LayoutParams lp = (LayoutParams) child.getLayoutParams();
        
        // 读取自定义布局参数
        int gravity = lp.customGravity;
        // ...
    }
}

@Override
protected LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams(MATCH_PARENT, MATCH_PARENT);
}

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

public static class LayoutParams extends ViewGroup.LayoutParams {
    public int customGravity;
    
    public LayoutParams(Context c, AttributeSet attrs) {
        super(c, attrs);
        TypedArray a = c.obtainStyledAttributes(
                attrs, R.styleable.CustomLayout_Layout);
        customGravity = a.getInt(
                R.styleable.CustomLayout_Layout_layout_customGravity, 
                0);
        a.recycle();
    }
}

三、最佳实践

  1. 性能优化:

    • 在onDraw()外解析属性值
    • 避免在draw()或layout()中频繁读取属性
  2. 兼容性处理:

    // 检查属性是否存在
    if (a.hasValue(R.styleable.CustomTextView_customText)) {
        mCustomText = a.getString(R.styleable.CustomTextView_customText);
    }
  3. 文档注释:

    <!-- 
      customMode: 控制文本显示模式
      0 - normal (默认)
      1 - bold
      2 - italic
    -->
    <attr name="customMode" format="enum">
        <enum name="normal" value="0"/>
        <enum name="bold" value="1"/>
        <enum name="italic" value="2"/>
    </attr>
  4. 属性组合:

    <attr name="customStyle" format="reference"/>
    
    <!-- 在style中组合多个属性 -->
    <style name="MyCustomStyle">
        <item name="customTextColor">#FF9800</item>
        <item name="customMode">bold</item>
    </style>
    
    <!-- 使用 -->
    <CustomTextView
        app:customStyle="@style/MyCustomStyle"
        ... />
  5. 主题属性引用:

    <!-- 定义主题属性 -->
    <attr name="colorPrimary" format="color"/>
    
    <!-- 在主题中设置 -->
    <style name="AppTheme" parent="Theme.Material.Light">
        <item name="colorPrimary">#3F51B5</item>
    </style>
    
    <!-- 在自定义属性中引用 -->
    <attr name="customHeaderColor" format="reference|color"/>
    
    <!-- 使用 -->
    <CustomTextView
        app:customHeaderColor="?attr/colorPrimary"
        ... />

通过以上方法,你可以创建出高度可定制、易维护的自定义View组件。记得在实现后充分测试各种属性组合的效果,确保在不同Android版本和设备上表现一致。