Android自定义控件3:带边框点击背景变色的textview,原型是支付宝手机充值中话费充值按钮

一、原型:支付宝手机充值中话费充值按钮。
二、实现效果:


三、在res/drawable下面创建两个xml:

custom_border_txt_bg.xml用户可点击时,点击切换背景

custom_border_txt_bg2.xml不可以点击时,自定义的背景

custom_border_txt_bg.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@null" android:state_pressed="true">
        <shape android:shape="rectangle">
            <padding android:bottom="7dp" android:left="7dp" android:right="7dp" android:top="7dp" />
            <corners android:radius="5dp" />
            <stroke android:width="1dp" android:color="@color/border_txt_color_default" />
            <solid android:color="@color/border_txt_color_default" />
        </shape>
    </item>
    <item android:drawable="@null">
        <shape android:shape="rectangle">
            <padding android:bottom="7dp" android:left="7dp" android:right="7dp" android:top="7dp" />
            <corners android:radius="5dp" />
            <stroke android:width="1dp" android:color="@color/border_txt_color_default" />
        </shape>
    </item>
</selector>

custom_border_txt_bg2.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <padding
        android:bottom="7dp"
        android:left="7dp"
        android:right="7dp"
        android:top="7dp" />
    <corners android:radius="5dp" />
    <stroke
        android:color="@color/gray"
        android:width="1dp" />
</shape>


四、在res/values/attrs.xml中自定义属性

    <!-- 带边框点击背景变色的textview -->
    <declare-styleable name="borderTxt">
        <attr name="bt_checkable" format="boolean"/><!-- 能否选择 -->
        <attr name="bt_unCheckedColor" format="reference|color"/><!-- bt_checkable=true时,未选中时颜色 -->
        <attr name="bt_checkedColor" format="reference|color"/><!-- bt_checkable=true时,选中时文本颜色 -->
        <attr name="bt_text1" format="string"/><!-- 第一行文本内容 -->
        <attr name="bt_textSize1" format="dimension"/><!-- 第一行字体大小 -->
        <attr name="bt_text2" format="string"/><!-- 第二行文本内容 -->
        <attr name="bt_textSize2" format="dimension"/><!-- 第二行字体大小 -->
    </declare-styleable>

五、自定义控件代码。

package com.custom.controls.button;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.custom.R;
import com.custom.utils.StringUtil;

/**
 * 带边框点击背景变色的textview
 * Created by Kevin on 2016/5/16.
 */
public class BorderTxt extends RelativeLayout implements View.OnTouchListener {
    //不可以点击时,初始颜色
    private int CHECKABLE_FALSE_DEFAULT = getResources().getColor(R.color.gray);
    //可以点击时,初始颜色
    private int CHECKABLE_TRUE_DEFAULT = getResources().getColor(R.color.border_txt_color_default);
    //可以点击时,选中时文本颜色
    private int CHECKABLE_TRUE_TXT = getResources().getColor(R.color.white);

    //是否可以点击
    private boolean checkable;
    /**
     * checkable=false时:unCheckedColor代表不可点击时的初始颜色;
     * checkable=true时:unCheckedColor代表可点击时的初始颜色;checkedColor表示选中时的文字颜色
     */
    private int unCheckedColor,checkedColor;
    private String text1, text2;//文本内容
    private float textSize1, textSize2;//文字大小
    private TextView txt1, txt2;//文本
    private LayoutParams txt1Params, txt2Params, relParams;//位置属性
    private RelativeLayout relativeLayout;//中间两行文本防止在此布局中,便于定位
    private PointF pointF;//存储手指按下的位置
    private Context mContext;//上下文

    public BorderTxt(Context context) {
        super(context);
    }

    public BorderTxt(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public BorderTxt(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        pointF = new PointF();//初始化手指按下时的坐标对象
        //获取自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.borderTxt);
        unCheckedColor = array.getColor(R.styleable.borderTxt_bt_unCheckedColor, CHECKABLE_TRUE_DEFAULT);
        checkedColor = array.getColor(R.styleable.borderTxt_bt_checkedColor, CHECKABLE_TRUE_TXT);
        checkable = array.getBoolean(R.styleable.borderTxt_bt_checkable, false);
        text1 = array.getString(R.styleable.borderTxt_bt_text1);
        text2 = array.getString(R.styleable.borderTxt_bt_text2);
        textSize1 = array.getDimensionPixelSize(R.styleable.borderTxt_bt_textSize1, 14);
        textSize2 = array.getDimensionPixelSize(R.styleable.borderTxt_bt_textSize2, 10);
        //自定义属性获取完之后,及时回收
        array.recycle();
        //初始化背景、文字颜色及事件绑定
        initUi();
        //控件位置布局
        relativeLayout = new RelativeLayout(mContext);
        relParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        relParams.addRule(CENTER_IN_PARENT);
        relativeLayout.setLayoutParams(relParams);

        txt1 = new TextView(mContext);
        txt1.setId(StringUtil.generateViewId());
        txt1.setText(text1);
        txt1.setTextColor(unCheckedColor);
        txt1.setTextSize(textSize1);
        //当第二行文本内容为空时,第一行居中显示,否则,将整体居中
        if(StringUtil.isEmpty(text2)){
            txt1Params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
            txt1Params.addRule(RelativeLayout.CENTER_IN_PARENT);
        }else{
            txt1Params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
            txt1Params.addRule(RelativeLayout.CENTER_HORIZONTAL);
            txt1.setGravity(Gravity.CENTER);

            txt2 = new TextView(mContext);
            txt2.setText(text2);
            txt2.setTextSize(textSize2);
            txt2.setTextColor(unCheckedColor);

            txt2Params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
            txt2Params.addRule(RelativeLayout.CENTER_HORIZONTAL);
            txt2Params.addRule(BELOW, txt1.getId());
            relativeLayout.addView(txt2, txt2Params);
        }
        //初始化文本颜色
        setTxtColor(unCheckedColor);
        relativeLayout.addView(txt1, txt1Params);
        addView(relativeLayout);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        /**
         * 可以点击时,手指第一次按下时存储此时的位置坐标;
         * 当手指抬起或者移动与按下时的坐标任意方向上的长度大于三分之一控件长度时,
         * 控件的press效果失效,并改变文本颜色。
         */
        if(checkable){
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                pointF.x = event.getX();
                pointF.y = event.getY();
                //按下
                setTxtColor(checkedColor);
            } else if (event.getAction() ==  MotionEvent.ACTION_UP
                    || getWidth() / 3 - Math.abs(event.getX() - pointF.x) < 0
                    || getHeight() / 3 - Math.abs(event.getY() - pointF.y) < 0
                    ) {
                //抬起
                this.setPressed(false);
                setTxtColor(unCheckedColor);
            }
        }
        return false;
    }

    /**
     * 设置文本颜色
     * @param color
     */
    public void setTxtColor(int color){
        txt1.setTextColor(color);
        if(!StringUtil.isEmpty(text2)){
            txt2.setTextColor(color);
        }
    }

    /**
     * 得到是否可以点击
     * @return
     */
    public boolean isCheckable() {
        return checkable;
    }

    /**
     * 设置是否可以点击
     * 并刷新控件状态
     * @param checkable
     */
    public void setCheckable(boolean checkable) {
        this.checkable = checkable;
        refreshDrawableState();//执行改变逻辑
        setTxtColor(unCheckedColor);//初始化文字颜色
    }

    /**
     * 执行控件改变
     */
    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        //改变时的切换逻辑
        initUi();
    }

    /**
     * 初始化背景、文字颜色及事件绑定
     */
    private void initUi() {
        if(checkable){
            unCheckedColor = CHECKABLE_TRUE_DEFAULT;
            checkedColor = CHECKABLE_TRUE_TXT;
            setBackgroundResource(R.drawable.custom_border_txt_bg);
            setOnTouchListener(this);
            /**
             * 绑定点击事件监听,否则点击时无法切换背景,selector效果无效;
             * 我的理解就是让空间拥有获得焦点的能力,尽管什么都没做
             */
            this.setOnClickListener(null);
        }else{
            unCheckedColor = CHECKABLE_FALSE_DEFAULT;
            setBackgroundResource(R.drawable.custom_border_txt_bg2);
        }
    }
}

六、res\layout中定义布局文件:activity_border_txt.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:borderTxt="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.custom.controls.button.BorderTxt
        android:id="@+id/btt"
        android:layout_width="200dp"
        android:layout_height="120dp"
        android:layout_centerHorizontal="true"
        android:layout_gravity="center"
        borderTxt:bt_text1="第一行"
        borderTxt:bt_text2="第二行" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="20dp"
        android:orientation="horizontal">

        <com.custom.controls.button.BorderTxt
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_centerHorizontal="true"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:layout_weight="1"
            borderTxt:bt_text1="10元"
            borderTxt:bt_text2="售价10.00元" />

        <com.custom.controls.button.BorderTxt
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_centerHorizontal="true"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:layout_weight="1"
            borderTxt:bt_checkable="true"
            borderTxt:bt_text1="20元"
            borderTxt:bt_text2="售价20.00元" />

        <com.custom.controls.button.BorderTxt
            android:id="@+id/border_txt_flow"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_centerHorizontal="true"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:layout_weight="1"
            borderTxt:bt_text1="30M" />
    </LinearLayout>

    <Button
        android:id="@+id/btn_change_border_txt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="改变按钮可选与否"></Button>
</LinearLayout>




七、注意

1、颜色是在colors.xml中自定义的,

R.color.gray  #C0C0C0
R.color.border_txt_color_default #3399FF
R.color.white #FFFFFF

2、this.setOnClickListener(null);一定要写,绑定点击事件监听,否则点击时无法切换背景,selector效果无效;

我的理解就是让空间拥有获得焦点的能力,尽管什么都没做。

3、注意setCheckable()和drawableStateChanged()中的逻辑位置,此例中
setCheckable()中的setTxtColor(unCheckedColor)如果放到drawableStateChanged()中的话,会因为drawableStateChanged()的重复调用而导致预期结果异常。
4、使用时,一定要加上命名空间
xmlns:borderTxt="http://schemas.android.com/apk/res-auto"

5、StringUtils工具:

package com.custom.utils;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by Kevin on 2016/5/13.
 */
public class StringUtil {

    //结合下面的方法可以来或者随机并不重复的控件ID
    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);

    public static int generateViewId() {
        for (; ; ) {
            final int result = sNextGeneratedId.get();
            // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
            int newValue = result + 1;
            if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
            if (sNextGeneratedId.compareAndSet(result, newValue)) {
                return result;
            }
        }
    }

    public static boolean isEmpty(String str) {
        if (null == str || "".equals(str.trim())) {
            return true;
        }else{
            return false;
        }
    }
}



以下问题可以直接点击了解:





  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值