zoukankan      html  css  js  c++  java
  • android自定义控件(1)-自定义控件属性

    那么还是针对我们之前写的自定义控件:开关按钮为例来说,在之前的基础上,我们来看看有哪些属性是可以自定义的:按钮的背景图片,按钮的滑块图片,和按钮的状态(是开还是关),实际上都应该是可以在xml文件中直接定义的。

    不妨先来看看之前我们在代码中不依靠自定义属性的时候,是如何写的,我们可以在initView方法中找到这样几行代码:

    backgroundBitmap = BitmapFactory.decodeResource(getResources(),
             R.drawable.switch_background);
             
    slideButton = BitmapFactory.decodeResource(getResources(),
             R.drawable.slide_button);
             
    currentState=false;

    会发现,我们是直接引用的资源文件,而不是在布局xml文件中使用的定义属性的方式,下面我们一步步来看看要怎么样做才可以定义使用上自定义属性:

    第一步:在res/values文件夹中添加attrs.xml文件

    实际上,这个文件名并不一定要写成attrs.xml,但是按照android源码的写法并且也便于别人查看代码的时候明确这个文件的用意,还是写成attrs.xml。

    下面要如何写呢,我们还是可以参看一下安卓的源码:打开源码文件夹下frameworksasecore es esvaluesattrs.xml文件,我们会发现这里面定义了很多attr的标签,里面不乏一些我们常见的属性:

    <attr name="layout_width" format="dimension">

    等等,在layout_width这个标签上面我们还可以发现一个

    <declare-styleable name="ViewGroup_Layout">

    declare-styleable标签的里包含了很多根ViewGruop相关的属性。

    而在这个attrs.xml文件的最外面,是一个<resources>的标签

    到这里,我们基本上就明白了一个attrs.xml文件的结构了:

    首先要一个<resources>的父标签,然后里面可以包含一个declare-styleable的标签,在这个标签里面我们再定义出三个attr 标签,分别代表我们需要定义的三个属性:按钮的背景图片,按钮的滑块图片,和按钮的状态;那么剩下的一个问题就是attr标签中的format代表什么意思。实际上format代表的是这条属性的值的类型:  

         1.reference:参考指定Theme中资源ID,这个类型意思就是你传的值可以是引用资源  
         2.string:字符串,如果你想别人既能直接写值也可以用类似"@string/test"引用资源的方式,可以写成format="string|reference"  
         3.Color:颜色  
         4.boolean:布尔值  
         5.dimension:尺寸值  
         6.float:浮点型  
         7.integer:整型  
         8.fraction:百分数  
         9.enum:枚举 ,如果你提供的属性只能让别人选择,不能随便传入,就可以写成这样  
            <attr name="language">  
                    <enum name="china" value="1"/>  
                    <enum name="English" value="2"/>  
            </attr>  

         10.flag:位或运算

    declare-styleable子元素:  
      
    定义一个styleable对象,每个styleable对象就是一组attr属性的集合 注意:这里的name属性并不是一定要和自定义类名相同,只是为了好区分对应类的属性而已  
      
      
    注意:上面的属性资源文件定义了该属性之后,至于到底是哪个自定义View组件中来使用该属性,该属性到底能发挥什么作用, 就不归该属性资源文件管了,也就是说这个属性资源文件是个公共的,大家都可以用,但是为了方便管理,一般都是一个自定义View里的属性写成一个declare-styleable集合.属性资源所定义的属性到底可以返回什么作用,取决于自定义组件的代码实现  

    在这里,我们的attrs.xml文件写成下面这样:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        
        <declare-styleable name="DJSwitch">
            
            <attr name="current_state" format="boolean"/>
            <attr name="backgroundImage" format="reference"/>
            <attr name="slideImage" format="reference"/>
            
        </declare-styleable>
        
    </resources>

    第二步:在自定义控件的类中拿到attrs.xml文件中定义的属性的对应的值,然后赋值给我们需要设置的变量,在这里就是 背景图片,滑块图片和开关状态 这三个值。

    要如何做呢?我们先将上面给出的

    backgroundBitmap = BitmapFactory.decodeResource(getResources(),
             R.drawable.switch_background);
             
    slideButton = BitmapFactory.decodeResource(getResources(),
             R.drawable.slide_button);
             
    currentState=false;

    这三句注释掉,然后换成下面的代码

    /**
         * 初始化View
         */
        private void initView(Context context, AttributeSet attrs) {
            /*
             * 从资源库中加载一个image对象
             * ios   UIImage *backgroundImage = [UIImage imageNamed:@"app_switch_bg"]; 
             * 也就是说,android里面的图像实体bitmap,可以当成是ios中的UIImage
             * 区别在于,ios中UIImage可以通过类方法来获取,android里面则是通过工厂方法来实现
             */
            /*switch_bg_img = BitmapFactory.decodeResource(getResources(), R.drawable.app_switch_bg);
            switch_btn_img = BitmapFactory.decodeResource(getResources(), R.drawable.app_switch_btn);
            */
            
            
            // 可以把这个TypedArray看成是一个包含了DJSwitch属性集合的HashMap
            TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.DJSwitch);
            // 获取该集合中共有多少个index
            int indexCount = typedArray.getIndexCount();
        
            for (int i = 0; i < indexCount; i++) {
                int id = typedArray.getIndex(i);
                switch (id) {
                case R.styleable.DJSwitch_backgroundImage:
                    switch_bg_img =  ((BitmapDrawable)typedArray.getDrawable(id)).getBitmap();
                    break;
                case R.styleable.DJSwitch_slideImage:
                    switch_btn_img = ((BitmapDrawable)typedArray.getDrawable(id)).getBitmap();
                    break;
                case R.styleable.DJSwitch_current_state:
                    currentState = typedArray.getBoolean(id, false);
                    break;
                default:
                    break;
                }
            }
            
            switchBtnMaxSlideDistance = switch_bg_img.getWidth() - switch_btn_img.getWidth();
            if (currentState) {
                switchBtnX = switchBtnMaxSlideDistance;
            } else {
                switchBtnX = 0;
            }
            typedArray.recycle();
            // 添加监听
            setOnClickListener(new MyOnSwitchClickListener()); // 可以理解为ios中的addTarget方法,或addGestureRecognizer
        }

    注释上面已经写得很清楚了,TypedArray会把XML文件所引用的自定义属性和值保存在一个Map表中,因此我们可以根据该Map的键(即:属性的ID)取出该属性对应的值。

    第三步:在布局文件中使用自定义属性,并设置属性值:

    <RelativeLayout 
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:alex="http://schemas.android.com/apk/res/com.example.test"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
        
        
        <!-- 为了清楚的看见该View的大小及位置,给其定义背景 -->
        <com.example.test.view.DJSwitch
               android:id="@+id/sw_switch"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#ff0000"
            android:layout_centerInParent="true"
            alex:current_state="false"
            alex:backgroundImage="@drawable/app_switch_bg"
            alex:slideImage="@drawable/app_switch_btn"
            />
    
        
    </RelativeLayout>

    在上面的代码,我们发现我们写成了alex:这样的标头,这个实际上是命名空间的简写(默认是app),所以我们必须要添加一个命名空间,参看一下Android的命名空间是如何写的:

    xmlns:android="http://schemas.android.com/apk/res/android"

    在这里xmlns:android里面的android,是可以变化的,这里我们就改为alex,然后对于http://schemas.android.com/apk/res/android这个部分,最后的android也要改的,这里必须改成整个应用的包名,我们可以去清单文件中查找,这里是com.example.togglebutton,所以整个写下来就是:xmlns:alex="http://schemas.android.com/apk/res/com.example.test"

    于是这里一个完整的布局文件写为:

    <RelativeLayout 
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:alex="http://schemas.android.com/apk/res/com.example.test"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
        
        
        <!-- 为了清楚的看见该View的大小及位置,给其定义背景 -->
        <com.example.test.view.DJSwitch
               android:id="@+id/sw_switch"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#ff0000"
            android:layout_centerInParent="true"
            alex:current_state="false"
            alex:backgroundImage="@drawable/app_switch_bg"
            alex:slideImage="@drawable/app_switch_btn"
            />
    
        
    </RelativeLayout>

    至此,在XML自定义按钮属性的基本步骤已经全部完毕了。不过如果我们想同时在代码中设置该按钮的属性该如何做呢?,很简单,在代码中为这三个自定义的按钮属性分别建立一个成员变量。

    DJSwitch.java

    /**
         * 标识当前按钮状态
         * 类似于iOS中.h文件里声明property 为BOOL类型的属性
         */
        private boolean currentState;
        /** 滑动按钮背景 */
        private Bitmap switch_bg_img;
        /** 滑动按钮滑块 */
        private Bitmap switch_btn_img;

    同时提供三个接口方法

    /**
         * 设置当前按钮开关状态
         * @param isOpen true: 开,false: 关
         */
        public void setCurrentSwitchState(boolean isOpen) {
            this.currentState = isOpen;
            if (isOpen) {
                switchBtnX = switchBtnMaxSlideDistance;
            } else {
                switchBtnX = 0;
            }
            invalidate();
        }
        
        /**
         * 设置当前按钮背景图片
         * @param resId 资源ID
         */
        public void setBackgroundImage(int resId) {
            switch_bg_img = BitmapFactory.decodeResource(getResources(), resId);
            // 因背景图片更改,须同时更新滑块最大移动距离
            switchBtnMaxSlideDistance = switch_bg_img.getWidth() - switch_btn_img.getWidth();
            invalidate(); // 相当于iOS中setNeedDisplay方法
        }
        
        
        /**
         * 设置滑动按钮图片
         * @param resId 资源ID
         */
        public void setSlideBtnImage(int resId) {
            switch_btn_img = BitmapFactory.decodeResource(getResources(), resId);
            switchBtnMaxSlideDistance = switch_bg_img.getWidth() - switch_btn_img.getWidth();
            invalidate();
        }

    MainActivity.java

    package com.example.test;
    
    import com.example.test.view.DJSwitch;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    public class MainActivity extends Activity {
    
        private DJSwitch sw_switch;
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initUIView();
        }
    
        
        private void initUIView() {
            sw_switch = (DJSwitch) findViewById(R.id.sw_switch);
            sw_switch.setCurrentSwitchState(true);
            sw_switch.setSlideBtnImage(R.drawable.ic_launcher);
        }
    
    }

    最终效果:

  • 相关阅读:
    让unidac支持加密的sqlite
    hook api 保护进程
    Delphi实现网页采集
    UNIDAC
    Delphi的视频捕获组件
    删除程序自身
    一种简单的自校验的注册码生成方案以及暗桩方法
    SQL server表字段信息说明
    淘宝API开发(一)简单介绍淘宝API功能接口作用
    淘宝API开发(二)淘宝API接口功能测试
  • 原文地址:https://www.cnblogs.com/yongdaimi/p/6136717.html
Copyright © 2011-2022 走看看