Android自定义控件——自定义组合控件

本文介绍了如何在Android开发中创建自定义组合控件,以提高代码复用性和可读性。通过自定义XML布局文件、Java代码以及定义styleable属性,实现了一个自定义组合控件。文章附带了使用示例和效果展示,旨在提供一种解决重复控件的方案,提升开发效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近整理项目时看到很多零碎东西都可以整理出来,写一个小demo可以学习一下或者以后需要了可以转过来看看就当复习资料了

这篇文章主要是Android自定义控件的组合控件,一个项目中如果有很多类似或者相同的控件,重复copy布局文件中的xml代码不仅显得布局文件比较复杂,而且会降低代码的可读性,光是控件的findViewById想想都觉得烦,最近整理了一下资料可以用来参考啦,可以提高一下开发效率,可能也会有很多人觉得没有必要,如果代码相似完全可以用include标签来进行解决,但是有时候有些需求不能单纯使用include标签来解决,其实从事Android开发这几年来深有体会,解决问题的方式有很多种,主要看开发人员怎么去更好的解决当前问题并且能够尽可能的去适配那些可能需要更改的需求,这就是所谓的可扩展吧,当然了这只是个人理解,最近废话比较多,不说了专心撸代码

先上效果图:

控件大致布局:

1,组合控件的xml布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:descendantFocusability="afterDescendants"
    android:gravity="center"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/left_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        android:textColor="#000000"
        android:textSize="16sp" />

    <com.example.testdemo.view.ClearEditText
        android:id="@+id/center_et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_weight="1"
        android:background="@null"
        android:ellipsize="end"
        android:hint=""
        android:lines="1"
        android:textColor="#888888"
        android:textColorHint="#BBBBBB"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/unit_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="2dp"
        android:text=""
        android:textColor="#888888"
        android:textSize="16sp"
        android:visibility="gone" />

    <ImageView
        android:id="@+id/right_iv"
        android:layout_width="15dp"
        android:layout_height="15dp"
        android:layout_marginLeft="15dp"
        android:src="@drawable/jiantou_right" />

</LinearLayout>

2,组合控件的Java文件:

package com.example.testdemo.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.Nullable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.example.testdemo.R;

public class InputLinearLayout extends LinearLayout {

    //左侧显示的textview内容
    private String leftString;
    //中间输入框的暗示内容
    private String hintString;
    //右侧单位内容
    private String unitString;
    //右侧箭头显示标识
    private boolean showRightIcon;
    //中间输入框显示位置
    private int gravityET;
    //中间输入框输入类型
    private int inputType;
    //中间输入框最长输入length
    private Integer maxLength;
    //中间输入框是否可编辑
    private boolean editEnabled;
    //左侧字体大小
    private float leftSize;
    //中间输入框字体大小
    private float centreSize;
    //左侧TextView控件
    private TextView leftTV;
    //中间ClearEditText控件
    private ClearEditText centreET;
    //右侧向右箭头
    private ImageView rightIV;
    //右侧单位
    private TextView unitTV;
    //中间输入框是否影藏清空按钮
    private boolean hideCleanIcon;

    public InputLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView(context, attrs);
        setView(context);
        initInputType();
    }

    private void initView(Context context, AttributeSet attrs) {
        //从xml的属性中获取到字体颜色与string
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.InputLinearLayout);
        leftString = ta.getString(R.styleable.InputLinearLayout_leftText);
        hintString = ta.getString(R.styleable.InputLinearLayout_hintText);
        unitString = ta.getString(R.styleable.InputLinearLayout_unitText);
        showRightIcon = ta.getBoolean(R.styleable.InputLinearLayout_rightShow, false);
        gravityET = ta.getInt(R.styleable.InputLinearLayout_gravityET, 3);
        leftSize = ta.getDimensionPixelOffset(R.styleable.InputLinearLayout_leftSize, dip2px(context, 16));
        centreSize = ta.getDimensionPixelOffset(R.styleable.InputLinearLayout_centreSize, dip2px(context, 16));
        inputType = ta.getInt(R.styleable.InputLinearLayout_centreInputType, 1);
        maxLength = ta.getInteger(R.styleable.InputLinearLayout_centreMaxLength, 40);
        editEnabled = ta.getBoolean(R.styleable.InputLinearLayout_editEnabled, true);
        hideCleanIcon = ta.getBoolean(R.styleable.InputLinearLayout_hideCleanIcon, false);
        ta.recycle();
    }

    private void setView(Context context) {
        //获取到控件
        LayoutInflater.from(context).inflate(R.layout.input_linear_layout, this);
        leftTV = (TextView) findViewById(R.id.left_tv);
        centreET = (ClearEditText) findViewById(R.id.center_et);
        rightIV = (ImageView) findViewById(R.id.right_iv);
        unitTV = (TextView) findViewById(R.id.unit_tv);
        //将控件与设置的xml属性关联
        leftTV.setText(leftString);
        centreET.setHint(hintString);
        centreET.setGravity(gravityET);
        centreET.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
        //中间的EditText不能编辑
        if (!editEnabled) {
            centreET.setEnabled(false);
            centreET.setHaveFocus(false);
        }
        if (!TextUtils.isEmpty(unitString)) {
            unitTV.setVisibility(VISIBLE);
            unitTV.setText(unitString);
        }
        centreET.showCleanBtn(hideCleanIcon);
        rightIV.setVisibility(showRightIcon ? VISIBLE : GONE);
        leftTV.setTextSize(px2dip(context, leftSize));
        centreET.setTextSize(px2dip(context, centreSize));
    }

    /**
     * 设置输入框的输入类型
     */
    public void initInputType() {
        switch (inputType) {
            case 1:
                centreET.setInputType(InputType.TYPE_CLASS_TEXT);
                break;
            case 2:
                centreET.setInputType(InputType.TYPE_CLASS_NUMBER);
                break;
            case 3:
                centreET.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL);
                break;
            case 4:
                centreET.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD);
                break;
            case 5:
                //直接设置TYPE_TEXT_VARIATION_PASSWORD密码明文显示
                centreET.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
                break;
            case 6:
                centreET.setInputType(InputType.TYPE_CLASS_PHONE);
                break;
            default:
                centreET.setInputType(InputType.TYPE_CLASS_TEXT);
                break;
        }
    }

    public TextView getLeftTV() {
        return leftTV;
    }

    public void setLeftTV(TextView leftTV) {
        this.leftTV = leftTV;
    }

    public ClearEditText getCentreET() {
        return centreET;
    }

    public void setCentreET(ClearEditText centreET) {
        this.centreET = centreET;
    }

    public ImageView getRightIV() {
        return rightIV;
    }

    public void setRightIV(ImageView rightIV) {
        this.rightIV = rightIV;
    }

    public TextView getUnitTV() {
        return unitTV;
    }

    /**
     * 根据手机的分辨率将px(像素)的单位转成dp
     * 可以根据自己需要重新写一个工具类直接调用,这里只是写一个demo方便而已
     *
     * @param context
     * @param pxValue
     * @return
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    /**
     * 根据手机的分辨率将dp的单位转成px(像素)
     *
     * @param context
     * @param dpValue
     * @return
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
}

上面Java文件中的style属性是如何通过组合控件的xml属性进行配置呢?来,下一步继续

3,在values中新建attrs.xml文件并在文件中定义组合控件的相关styleable属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 自定义输入控件布局  start -->
    <declare-styleable name="InputLinearLayout">
        <attr name="leftText" format="string" />
        <attr name="leftSize" format="dimension" />
        <attr name="hintText" format="string" />
        <attr name="centreSize" format="dimension" />
        <attr name="rightShow" format="boolean" />
        <attr name="gravityET">
            <flag name="right" value="5" />
            <flag name="left" value="3" />
        </attr>
        <attr name="centreInputType">
            <flag name="text" value="1" />
            <flag name="number" value="2" />
            <flag name="numberDecimal" value="3" />
            <flag name="numberPassword" value="4" />
            <flag name="textPassword" value="5" />
            <flag name="phone" value="6" />
        </attr>
        <attr name="centreMaxLength" format="integer" />
        <attr name="editEnabled" format="boolean" />
        <attr name="hideCleanIcon" format="boolean" />
        <attr name="unitText" format="string" />
    </declare-styleable>
    <!-- 自定义输入控件布局  end -->
</resources>

  到这里自定义组合控件的过程就基本结束了,接下来看看使用

首先在我们要使用的布局文件中添加该控件

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

    <com.example.testdemo.view.InputLinearLayout
        android:id="@+id/input_LL"
        android:layout_width="match_parent"
        android:layout_height="44dp"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        app:centreInputType="number"
        app:editEnabled="true"
        app:gravityET="right"
        app:hideCleanIcon="true"
        app:hintText="请输入价格"
        app:leftText="护肤品"
        app:rightShow="false"
        app:unitText="元" />

    <View
        android:layout_width="match_parent"
        android:layout_height="0.1dp"
        android:background="#bdbdbd" />

    <com.example.testdemo.view.InputLinearLayout
        android:id="@+id/input_LL2"
        android:layout_width="match_parent"
        android:layout_height="44dp"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        app:centreInputType="number"
        app:editEnabled="false"
        app:gravityET="right"
        app:hintText="请进入下个页面进行选择"
        app:leftText="衣服"
        app:rightShow="true"
        app:unitText="(件)" />

    <View
        android:layout_width="match_parent"
        android:layout_height="0.1dp"
        android:background="#bdbdbd" />

    <com.example.testdemo.view.InputLinearLayout
        android:id="@+id/input_LL1"
        android:layout_width="match_parent"
        android:layout_height="44dp"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        app:centreInputType="number"
        app:editEnabled="true"
        app:gravityET="right"
        app:hintText="请输入整数"
        app:leftText="饺子"
        app:rightShow="false"
        app:unitText="个" />


</LinearLayout>

运行就可以看到上面的效果图了,这基本就是个小笔记,下次再用到自定义组合控件一看就知道怎么用了,demo中用到了自定义的ClearEditText在此贴出相关代码

ClearEditText.java
package com.example.testdemo.view;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.animation.Animation;
import android.view.animation.CycleInterpolator;
import android.view.animation.TranslateAnimation;

import com.example.testdemo.R;

/**
 * 自定义带删除的EditText
 *
 * @author Ellen on 2019/5/12.
 */

public class ClearEditText extends android.support.v7.widget.AppCompatEditText implements
        OnFocusChangeListener, TextWatcher {
    /**
     * 删除按钮的引用
     */
    private Drawable mClearDrawable;
    /**
     * 控件是否有焦点
     */
    private boolean hasFoucs;
    /**
     * 输入表情前EditText中的文本
     */
    private String inputAfterText;
    /**
     * 是否重置了EditText的内容
     */
    private boolean resetText;

    private Context context;
    /**
     * 是否需要显示清楚btn
     */
    private boolean showClean = true;

    /**
     * 是否响应点击事件
     */
    private boolean haveFocus = true;

    public ClearEditText(Context context) {
        this(context, null);
        this.context = context;
    }

    public ClearEditText(Context context, AttributeSet attrs) {
        //这里构造方法也很重要,不加这个很多属性不能再XML里面定义
        this(context, attrs, android.R.attr.editTextStyle);
        this.context = context;
    }

    public ClearEditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.context = context;
        init();
    }


    private void init() {
        //获取EditText的DrawableRight,假如没有设置我们就使用默认的图片
        mClearDrawable = getCompoundDrawables()[2];
        if (mClearDrawable == null) {
            mClearDrawable = getResources().getDrawable(R.drawable.clean_smal);
        }
        mClearDrawable.setBounds(0, 0, mClearDrawable.getIntrinsicWidth(), mClearDrawable.getIntrinsicHeight());
        //默认设置隐藏图标
        setClearIconVisible(false);
        //设置焦点改变的监听
        setOnFocusChangeListener(this);
        //设置输入框里面内容发生改变的监听
        addTextChangedListener(this);
    }

    public void showCleanBtn(boolean showClean) {
        this.showClean = showClean;
    }

    /**
     * 因为我们不能直接给EditText设置点击事件,所以我们用记住我们按下的位置来模拟点击事件
     * 当我们按下的位置 在  EditText的宽度 - 图标到控件右边的间距 - 图标的宽度  和
     * EditText的宽度 - 图标到控件右边的间距之间我们就算点击了图标,竖直方向就没有考虑
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!haveFocus) return false;
        if (event.getAction() == MotionEvent.ACTION_UP) {
            if (getCompoundDrawables()[2] != null) {

                boolean touchable = event.getX() > (getWidth() - getTotalPaddingRight())
                        && (event.getX() < ((getWidth() - getPaddingRight())));

                if (touchable) {
                    this.setText("");
                }
            }
        }

        return super.onTouchEvent(event);
    }

    /**
     * 当ClearEditText焦点发生变化的时候,判断里面字符串长度设置清除图标的显示与隐藏
     */
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        this.hasFoucs = hasFocus;
        if (hasFocus) {
            setClearIconVisible(getText().length() > 0);
        } else {
            setClearIconVisible(false);
        }
    }


    /**
     * 设置清除图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去
     *
     * @param visible
     */
    protected void setClearIconVisible(boolean visible) {
        Drawable right = visible && showClean ? mClearDrawable : null;
        setCompoundDrawables(getCompoundDrawables()[0],
                getCompoundDrawables()[1], right, getCompoundDrawables()[3]);
    }


    /**
     * 当输入框里面内容发生变化的时候回调的方法
     */
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        if (hasFoucs) {
            setClearIconVisible(s.length() > 0);
        }
        if (!resetText) {
            resetText = true;
        } else {
            resetText = false;
        }
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        if (!resetText) {
            // 这里用s.toString()而不直接用s是因为如果用s,
            // 那么,inputAfterText和s在内存中指向的是同一个地址,s改变了,
            // inputAfterText也就改变了,那么表情过滤就失败了
            inputAfterText = s.toString();
        }
    }

    @Override
    public void afterTextChanged(Editable s) {

    }

    /**
     * 设置晃动动画
     */
    public void setShakeAnimation() {
        this.setAnimation(shakeAnimation(5));
    }

    /**
     * 晃动动画
     *
     * @param counts 1秒钟晃动多少下
     * @return
     */
    public static Animation shakeAnimation(int counts) {
        Animation translateAnimation = new TranslateAnimation(0, 10, 0, 0);
        translateAnimation.setInterpolator(new CycleInterpolator(counts));
        translateAnimation.setDuration(1000);
        return translateAnimation;
    }

    public void setHaveFocus(boolean haveFocus) {
        this.haveFocus = haveFocus;
    }
}

demo中的清除图片包括向右的箭头就不上传了,浪费资源,这种小图片可以去阿里矢量图库搜索下载,这不是打广告,只是觉得这个网站好用,不用再去找UI切图了,路漫漫,撸代码要加油了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值