【我的Android进阶之旅】解决Android Studio在XML中使用自定义View输入自定义属性的时候没有任何提示的问题

一、问题描述

这两天公司的一个年轻的小伙子在维护历史的自定义控件库的时候,发现之前开发这个自定义控件库的人员写的自定义控件,Android Studio开发的时候,在XML布局文件中无法自动提示出自定义属性,需要自己去看代码了解这个自定义控件有哪些自定义属性,然后手动的copy和paste代码。太恶心了!

经过一段时间的排查,发现之前的开发人员自定义控件的自定义属性的declare-styleable命名不规范导致的,后来修复了之后就可以正常显示了。

二、还原问题错误示范 与 自定义属性的正确示范

下面我拿我之前写过的自定义控件,将代码改为错误的示例之后,示范一下,如下图所示:

【我的Android进阶之旅】Android自定义电池控件(https://blog.csdn.net/ouyang_peng/article/details/80706889)

2.1 正确示范

之前我的文章写过一个自定义的电池控件

  • 原来的自定义控件的class名为:PowerConsumptionRankingsBatteryView
    这里写图片描述
/**
 * 自定义 耗电排行内的 电池图标
 * </p>
 * created by OuyangPeng at 2018/6/10 下午 05:57
 *
 * @author OuyangPeng
 */
public class PowerConsumptionRankingsBatteryView extends View {

引入自定义属性部分的代码如下所示:

这里写图片描述

/**
     * 初始化自定义属性
     */
    private void initTypeArray(Context context, @Nullable AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PowerConsumptionRankingsBatteryView);
        lowerPowerColor = typedArray.getColor(R.styleable.PowerConsumptionRankingsBatteryView_batteryLowerPowerColor,
                getResources().getColor(R.color.lowerPowerColor));
        onlineColor = typedArray.getColor(R.styleable.PowerConsumptionRankingsBatteryView_batteryOnlineColor,
                getResources().getColor(R.color.onlineColor));
        offlineColor = typedArray.getColor(R.styleable.PowerConsumptionRankingsBatteryView_batteryOfflineColor,
                getResources().getColor(R.color.offlineColor));
        //外壳的相关信息
        shellCornerRadius = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView_batteryShellCornerRadius,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_shell_corner));
        shellWidth = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView_batteryShellWidth,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_shell_width));
        shellHeight = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView_batteryShellHeight,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_shell_height));
        shellStrokeWidth = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView_batteryShellStrokeWidth,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_shell_stroke_width));

        //电池头的相关信息
        shellHeadCornerRadius = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView_batteryShellHeadCornerRadius,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_head_corner));
        shellHeadWidth = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView_batteryShellHeadWidth,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_head_width));
        shellHeadHeight = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView_batteryShellHeadHeight,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_head_height));

        //电池最大高度
        levelMaxHeight = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView_batteryLevelMaxHeight,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_level_max_height));
        //电池宽度
        levelWidth = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView_batteryLevelWidth,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_level_width));

        //电池外壳和电池等级直接的间距
        gap = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView_batteryGap,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_gap));
        //回收typedArray
        typedArray.recycle();
    }
  • 自定义控件的自定义属性的declare-styleablePowerConsumptionRankingsBatteryView

这里写图片描述

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PowerConsumptionRankingsBatteryView">
        <!-- 手表低电时候的电池颜色 -->
        <attr name="batteryLowerPowerColor" format="color" />
        <!-- 手表在线时候的电池颜色 -->
        <attr name="batteryOnlineColor" format="color" />
        <!-- 手表离线时候的电池颜色 -->
        <attr name="batteryOfflineColor" format="color" />
        <!--  电池最大高度 -->
        <attr name="batteryLevelMaxHeight" format="integer" />
        <!--  电池宽度 -->
        <attr name="batteryLevelWidth" format="integer" />
        <!--  外壳的相关信息 -->
        <attr name="batteryShellCornerRadius" format="dimension" />
        <attr name="batteryShellWidth" format="dimension" />
        <attr name="batteryShellHeight" format="dimension" />
        <attr name="batteryShellStrokeWidth" format="dimension" />
        <!--  电池头的相关信息 -->
        <attr name="batteryShellHeadCornerRadius" format="dimension" />
        <attr name="batteryShellHeadWidth" format="dimension" />
        <attr name="batteryShellHeadHeight" format="dimension" />
        <!-- 电池外壳和电池等级直接的间距 -->
        <attr name="batteryGap" format="dimension" />
    </declare-styleable>
</resources>

然后将该自定义控件,在XML布局文件中引用

这里写图片描述

如上图所示,Android Studio完全自动就把所有的自定义属性都提示处理啦。


2.2 错误示范

2.2.1 修改declare-styleable名为 PowerConsumptionRankingsBatteryView2

现在我来还原一下之前那位同事的错误示范,我将电池控件的自定义属性的declare-styleable名从 PowerConsumptionRankingsBatteryView改为 PowerConsumptionRankingsBatteryView2。这样就和自定义控件的名称不一样了。

这里写图片描述

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PowerConsumptionRankingsBatteryView2">
        <!-- 手表低电时候的电池颜色 -->
        <attr name="batteryLowerPowerColor" format="color" />
        <!-- 手表在线时候的电池颜色 -->
        <attr name="batteryOnlineColor" format="color" />
        <!-- 手表离线时候的电池颜色 -->
        <attr name="batteryOfflineColor" format="color" />
        <!--  电池最大高度 -->
        <attr name="batteryLevelMaxHeight" format="integer" />
        <!--  电池宽度 -->
        <attr name="batteryLevelWidth" format="integer" />
        <!--  外壳的相关信息 -->
        <attr name="batteryShellCornerRadius" format="dimension" />
        <attr name="batteryShellWidth" format="dimension" />
        <attr name="batteryShellHeight" format="dimension" />
        <attr name="batteryShellStrokeWidth" format="dimension" />
        <!--  电池头的相关信息 -->
        <attr name="batteryShellHeadCornerRadius" format="dimension" />
        <attr name="batteryShellHeadWidth" format="dimension" />
        <attr name="batteryShellHeadHeight" format="dimension" />
        <!-- 电池外壳和电池等级直接的间距 -->
        <attr name="batteryGap" format="dimension" />
    </declare-styleable>
</resources>

2.2.2 修改引入自定义属性的方法

然后对应的自定义View引入该自定义属性的代码也需要修改下,不然编译出错,如下所示
这里写图片描述

我们改为PowerConsumptionRankingsBatteryView2,然后重新编译

这里写图片描述

 /**
     * 初始化自定义属性
     */
    private void initTypeArray(Context context, @Nullable AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PowerConsumptionRankingsBatteryView2);
        lowerPowerColor = typedArray.getColor(R.styleable.PowerConsumptionRankingsBatteryView2_batteryLowerPowerColor,
                getResources().getColor(R.color.lowerPowerColor));
        onlineColor = typedArray.getColor(R.styleable.PowerConsumptionRankingsBatteryView2_batteryOnlineColor,
                getResources().getColor(R.color.onlineColor));
        offlineColor = typedArray.getColor(R.styleable.PowerConsumptionRankingsBatteryView2_batteryOfflineColor,
                getResources().getColor(R.color.offlineColor));
        //外壳的相关信息
        shellCornerRadius = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView2_batteryShellCornerRadius,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_shell_corner));
        shellWidth = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView2_batteryShellWidth,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_shell_width));
        shellHeight = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView2_batteryShellHeight,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_shell_height));
        shellStrokeWidth = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView2_batteryShellStrokeWidth,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_shell_stroke_width));

        //电池头的相关信息
        shellHeadCornerRadius = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView2_batteryShellHeadCornerRadius,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_head_corner));
        shellHeadWidth = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView2_batteryShellHeadWidth,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_head_width));
        shellHeadHeight = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView2_batteryShellHeadHeight,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_head_height));

        //电池最大高度
        levelMaxHeight = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView2_batteryLevelMaxHeight,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_level_max_height));
        //电池宽度
        levelWidth = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView2_batteryLevelWidth,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_level_width));

        //电池外壳和电池等级直接的间距
        gap = typedArray.getDimensionPixelOffset(R.styleable.PowerConsumptionRankingsBatteryView2_batteryGap,
                getResources().getDimensionPixelOffset(R.dimen.power_consumption_rankings_dimen_main_battery_view_gap));
        //回收typedArray
        typedArray.recycle();
    }

如上图所示,可以正常编译,但是会有警告,警告我们需要将custom view的名字和delare-styleabl的名字改为一样。提示如下

By convertion ,the custom view(PowerConsumptionRankingsBatteryView) and the
declare-styleable (PowerConsumptionRankingsBatteryView2) should have the same name (various editor features rely on this convention) less…(Ctrl + F1)
The convention for custom views is to use a declare-styleable whose name matches the custom view class name.The IDE relies on this convention such that for example code completion can be offered for attributes in a custom view in layout XML resource files.

(Similarly , layout parameter classes should use the suffix _Layout.)

这里写图片描述

上面的提示翻译成中文大致意思如下:

通过约定,自定义视图(PowerConsumptionRankingsBatteryView)和
declare-styleable(PowerConsumptionRankingsBatteryView2)应具有相同的名称(各种编辑器功能依赖于此约定)

自定义视图的约定是使用声明样式,其名称与自定义视图类名称匹配.IDE依赖于此约定,例如,可以为布局XML资源文件中的自定义视图中的属性提供代码完成。
(类似地,布局参数类应使用后缀_Layout。)

2.2.3 复现XML中不能自动提示自定义属性

1、敲完自定义命名空间 app 之后,Android Studio 没有任何提示。
然后我们看看,XML中是否会自动提示,如下所示:

这里写图片描述

如上图所示,我们敲了自定义命名空间 app 之后,Android Studio 没有任何提示。

2、敲完自定义命名空间 app 之后,敲自定义属性的相关名称过程中,没有任何提示,如下图所示

这里写图片描述

3、我们自己手动敲完自定义的属性,编译也是正常的。
这里写图片描述

这种感觉特别不爽,没有提示你都不知道自己是否敲对了。而且你还得找到对应的定义自定义属性的地方,复制过来然后粘贴上去,才能保证不出错。

三、总结

如Android Studio中提示的一样,我们在自定义View和自定义属性的时候,要保持自定义View的className自定义属性的的declare-styleable名一样,根据这个约定,IDE才会自动提示。
正确的姿势请参考2.1 正确示范所说的正确示范。

当遇到一些不可思议的bug的时候,请检查下代码,看看Android Studio给你的Warning和Error之类的提示,也许就会找到解决思路啦。


作者:欧阳鹏 欢迎转载,与人分享是进步的源泉!
转载请保留原文地址:https://blog.csdn.net/qq446282412/article/details/81744295

如果本文对您有所帮助,欢迎您扫码下图所示的支付宝和微信支付二维码对本文进行打赏。

这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq446282412/article/details/81744295