Android自定义控件(一)

1、创建新视图的类型与希望达到的目标:

  • 如果现有控件已经满足希望实现的基本功能,那么只需要对现有控件的外观和行为进行修改或扩展即可。通过重写事件处理程序和onDraw方法,但是仍然回调超类的方法,可以对视图进行定制,而不必重新实现它的功能。例如,定制一个TextView来显示指定位数的小数。
  • 可以通过组合多个视图创建不可分割的、可重用的控件,从而使它可以综合使用多个相互关联的视图的功能。例如,通过组合一个TextView和一个Button来创建一个秒表定时器,当单击它的时候,就重置计数器。
  • 当需要一个全新的界面,而通过修改或者组合现有控件不能是西安这个目标的时候,就可以创建一个全新的控件。
1.1 修改现有视图
在一个已有的控件基础上创建一个新视图,就需要创建一个扩展了原控件的新类。
/**
 * Desc: 在TextView的基础上绘制一条横线和竖线
 * Author: xss
 * Time:2016/1/5 10:25
 */
public class CustomTextView extends TextView {
    private Paint marginPaint;  //绘制边缘
    private Paint linePaint;   //绘制页面背景
    private int paperColor;    //页面颜色值
    private float margin;      //边缘宽度值

    public CustomTextView(Context context) {
        super(context);
        init();
    }

    public CustomTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

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

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        //获得对资源表的引用
        Resources mResources = getResources();

        //创建将在onDraw方法中使用的画刷
        marginPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        marginPaint.setColor(mResources.getColor(R.color.custom_textView_margin));

        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setColor(mResources.getColor(R.color.custom_textView_lines));

        //获得页面背景和边缘宽度
        paperColor = mResources.getColor(R.color.custom_textView_paper);
        margin = mResources.getDimension(R.dimen.custom_textView_margin);
    }

    /**
     * 一旦绘制了页面图像后,就可以调用超类的onDraw方法,让它像往常一样绘制文本
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        //绘制页面的颜色
        canvas.drawColor(paperColor);

        //绘制边缘
        canvas.drawLine(0, 0, 0, getMeasuredHeight(), linePaint); //绘制左高
        canvas.drawLine(0, getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight(), linePaint); //绘制底宽
        canvas.drawLine(margin, 0, margin, getMeasuredHeight(), marginPaint); //绘制margin

        //移动文本,让它跨过边缘
        canvas.save();
        canvas.translate(margin, 0);

        //使用TextView渲染文本
        super.onDraw(canvas);
        canvas.restore();
    }
}
实现效果图:

1.2 创建组合控件视图
复合控件是指不可分割的、自包含的视图组(ViewGroup),其中包含了多个排列和连接在一起的子视图。
当创建复合控件时,必须对它包含的视图的布局、外观和交互进行定义。复合控件是通过扩展一个ViewGroup(通常是一个布局)来创建的。因此,要创建一个新的复合控件,首先需要选择一个最合适放置子控件的布局类,然后扩展该类。
我们来实现这样的一个布局效果图:

首先,创建组合控件布局文件view_custom_layout.xml;
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        android:padding="8dp">
        <TextView
            android:id="@+id/tv_label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="label" />
        <EditText
            android:id="@+id/edt_content"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="#00ff00"
            android:hint="Please input"
            android:paddingLeft="10dp"
            android:paddingRight="10dp"
            android:gravity="center_vertical"
            android:focusable="true"
            android:textCursorDrawable="@drawable/color_cursor"/>
        <TextView
            android:id="@+id/tv_extra"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="unit" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/ll_span_line"
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="#bbb"
        android:orientation="vertical"/>
</merge>
其次,自定义控件的一些属性,在attr.xml中定义如下:
    <declare-styleable name="CustomLinearLayout">
        <attr name="custom_label_text" format="string" />
        <attr name="custom_label_textSize" format="dimension" />
        <attr name="custom_label_textColor" format="color" />
        <attr name="custom_label_padding" format="dimension" />
        <attr name="custom_edt_text" format="string" />
        <attr name="custom_edt_textSize" format="dimension" />
        <attr name="custom_edt_textColor" format="color" />
        <attr name="custom_edt_hint_text" format="string" />
        <attr name="custom_edt_hint_textColor" format="color" />
        <attr name="custom_edt_padding" format="dimension" />
        <attr name="custom_edt_marginLeft" format="dimension" />
        <attr name="custom_edt_marginRight" format="dimension" />
        <attr name="custom_extra_text" format="string" />
        <attr name="custom_extra_textSize" format="dimension" />
        <attr name="custom_extra_textColor" format="color" />
        <attr name="custom_extra_background" format="reference|color" />
        <attr name="custom_line_visibility">
            <enum name="VISIBLE" value="0" />
            <enum name="INVISIBLE" value="1" />
            <enum name="GONE" value="8" />
        </attr>
        <attr name="custom_edt_inputType">  <!--整形定义  format="integer"-->
            <flag name="none" value="0x00000000" />
            <flag name="text" value="0x00000001" />
            <flag name="textCapCharacters" value="0x00001001" />
            <flag name="textMultiLine" value="0x00020001" />
            <flag name="textUri" value="0x00000011" />
            <flag name="textEmailAddress" value="0x00000021" />
            <flag name="textPersonName" value="0x00000061" />
            <flag name="textPassword" value="0x00000081" />
            <flag name="number" value="0x00000002" />
            <flag name="numberDecimal" value="0x00002002" />
            <flag name="numberPassword" value="0x00000012" />
            <flag name="phone" value="0x00000003" />
            <flag name="datetime" value="0x00000004" />
            <flag name="date" value="0x00000014" />
            <flag name="time" value="0x00000024" />
        </attr>
        <attr name="custom_edt_imeOptions" >
            <flag name="normal" value="0x00000000" />
            <flag name="actionUnspecified" value="0x00000000" />
            <flag name="actionNone" value="0x00000001" />
            <flag name="actionGo" value="0x00000002" />
            <flag name="actionSearch" value="0x00000003" />
            <flag name="actionSend" value="0x00000004" />
            <flag name="actionNext" value="0x00000005" />
            <flag name="actionDone" value="0x00000006" />
        </attr>
        <attr name="custom_edt_background" format="color" />
    </declare-styleable>
最后,就是重写我们的布局,代码如下:
public class CustomLinearLayout extends LinearLayout {
    private Context context;

    private TextView tv_label;
    private EditText edt_content;
    private TextView tv_extra;
    private LinearLayout ll_span_line;

    private String labelText;
    private float labelTextSize;
    private int labelTextColor;
    private float labelPadding;

    private String contentText;
    private float contentTextSize;
    private int contentTextColor;
    private String hintText;
    private int hintTextColor;
    private float contentPadding;
    private float contentMarginLeft;
    private float contentMarginRight;
    private int edtInputType;
    private int edtImeOptions;
    private int edtBackground;

    private String extraText;
    private float extraTextSize;
    private int extraTextColor;
    private int extraBackground;

    private int lineVisibility;

    public CustomLinearLayout(Context context) {
        super(context);
        this.context = context;
        init();
    }

    public CustomLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        obtainStyleAttributes(context, attrs, 0, 0);
        init();
    }

    public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        obtainStyleAttributes(context, attrs, defStyleAttr, 0);
        init();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        this.context = context;
        obtainStyleAttributes(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    /**
     * 获取属性值
     * @param context
     * @param attrs
     * @param defStyleAttr
     * @param defStyleRes
     */
    private void obtainStyleAttributes(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomLinearLayout, defStyleAttr, defStyleRes);

        labelText = a.getString(R.styleable.CustomLinearLayout_custom_label_text);
        labelTextSize = a.getDimension(R.styleable.CustomLinearLayout_custom_label_textSize, 12f);
        labelTextColor = a.getColor(R.styleable.CustomLinearLayout_custom_label_textColor, Color.parseColor("#000000"));
        labelPadding = a.getDimension(R.styleable.CustomLinearLayout_custom_label_padding, 0f);

        contentText = a.getString(R.styleable.CustomLinearLayout_custom_edt_text);
        contentTextSize = a.getDimension(R.styleable.CustomLinearLayout_custom_edt_textSize, 12f);
        contentTextColor = a.getColor(R.styleable.CustomLinearLayout_custom_edt_textColor, Color.parseColor("#000000"));
        hintText = a.getString(R.styleable.CustomLinearLayout_custom_edt_hint_text);
        hintTextColor = a.getColor(R.styleable.CustomLinearLayout_custom_edt_hint_textColor, Color.parseColor("#000000"));
        contentPadding = a.getDimension(R.styleable.CustomLinearLayout_custom_edt_padding, 0f);
        contentMarginLeft = a.getDimension(R.styleable.CustomLinearLayout_custom_edt_marginLeft, 0f);
        contentMarginRight = a.getDimension(R.styleable.CustomLinearLayout_custom_edt_marginRight, 0f);
        edtInputType = a.getInt(R.styleable.CustomLinearLayout_custom_edt_inputType, EditorInfo.TYPE_NULL);  //EditorInfo.TYPE_NULL
        edtImeOptions = a.getInt(R.styleable.CustomLinearLayout_custom_edt_imeOptions, EditorInfo.IME_ACTION_NONE);
        edtBackground = a.getColor(R.styleable.CustomLinearLayout_custom_edt_background, Color.parseColor("#ffffff"));

        extraText = a.getString(R.styleable.CustomLinearLayout_custom_extra_text);
        extraTextSize = a.getDimension(R.styleable.CustomLinearLayout_custom_extra_textSize, 12f);
        extraTextColor = a.getColor(R.styleable.CustomLinearLayout_custom_extra_textColor, Color.parseColor("#000000"));
        extraBackground = a.getResourceId(R.styleable.CustomLinearLayout_custom_extra_background, R.color.COLOR_000000);

        lineVisibility = a.getInt(R.styleable.CustomLinearLayout_custom_line_visibility, View.VISIBLE);

        a.recycle();
    }

    /**
     * 获取控件对象
     */
    private void init() {
        setOrientation(LinearLayout.VERTICAL);
        setGravity(Gravity.CENTER_VERTICAL);

        LayoutInflater inflater = (LayoutInflater)this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.view_custom_layout, this, true);

        final ViewGroup viewGroup = (ViewGroup)getChildAt(0);
        if (viewGroup != null) {
            tv_label = (TextView)viewGroup.getChildAt(0);
            edt_content = (EditText)viewGroup.getChildAt(1);
            tv_extra = (TextView)viewGroup.getChildAt(2);
        }
        ll_span_line = (LinearLayout)getChildAt(1);

//        tv_label = (TextView)view.findViewById(R.id.tv_label);
//        edt_content = (EditText)view.findViewById(R.id.edt_content);
//        tv_extra = (TextView)view.findViewById(R.id.tv_extra);
//        ll_span_line = (LinearLayout)view.findViewById(R.id.ll_span_line);

        tv_label.setText(labelText);
        //setTextSize函数对应的单位本身就是sp
        tv_label.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelTextSize);
        tv_label.setTextColor(labelTextColor);
        tv_label.setPadding((int) labelPadding, (int) labelPadding, (int) labelPadding, (int) labelPadding);

        showKeyBoard();
        edt_content.setText(contentText);
        edt_content.setTextSize(TypedValue.COMPLEX_UNIT_PX, contentTextSize);
        edt_content.setTextColor(contentTextColor);
        edt_content.setHint(hintText);
        edt_content.setHintTextColor(hintTextColor);
        edt_content.setPadding((int) contentPadding, (int) contentPadding, (int) contentPadding, (int) contentPadding);
        LinearLayout.LayoutParams lp = (LayoutParams) edt_content.getLayoutParams();
        lp.setMargins((int) contentMarginLeft, 0, (int) contentMarginRight, 0);  //左上右下
        edt_content.setImeOptions(edtImeOptions);
        setEdtInputType();  //设置InputType
        edt_content.setBackgroundColor(edtBackground);
        if (!TextUtils.isEmpty(contentText)) {
            edt_content.setKeyListener(DigitsKeyListener.getInstance(contentText));
        }
        edt_content.setFocusableInTouchMode(true);

        tv_extra.setText(extraText);
        tv_extra.setTextSize(TypedValue.COMPLEX_UNIT_PX, extraTextSize);
        tv_extra.setTextColor(extraTextColor);
        tv_extra.setBackgroundColor(extraBackground); //getResources().getColor(extraBackground)

        if (lineVisibility == VISIBLE) {
            ll_span_line.setVisibility(VISIBLE);
        } else if (lineVisibility == INVISIBLE) {
            ll_span_line.setVisibility(INVISIBLE);
        } else if (lineVisibility == GONE) {
            ll_span_line.setVisibility(GONE);
        }
    }

    private void showKeyBoard() {
        if (edt_content != null) {
            edt_content.setFocusable(true);
            edt_content.setFocusableInTouchMode(true);
            edt_content.requestFocus();
            //调用系统输入法
            InputMethodManager inputMethodManager = (InputMethodManager) edt_content.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            inputMethodManager.showSoftInput(edt_content, 0);
        }
    }
}
1.3 创建定制的视图
要在一个空画布上创建新的控件,就需要对View或者Surface类(3D)进行扩展。View类提供一个Canvas和一系列绘制方法以及Paint类,因此,使用它可以运用位图和光栅图像创建一个可视化的界面,之后,可以重写像触摸屏或者按键按下这样的用户事件以提供交互。
例如,要实现以下效果的自定义视图:

代码实现如下:
public class CustomView extends View {
    private Paint mPaint;

    //使用代码进行创建时必需的构造函数
    public CustomView(Context context) {
        super(context);
        init();
    }

    //使用资源文件进行填充时必需的构造函数
    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    //使用资源文件进行填充时必需的构造函数
    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        //为提高效率,paint对象最好在构造函数中创建,因为在onDraw方法中创建的任何对象都会在屏幕刷新的时候被创建和销毁
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.WHITE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int height = getMeasuredHeight();
        int width = getMeasuredWidth();

        //找出控件的中心
        int px = width / 2;
        int py = height / 2;

        String text = "Hello world !";
        //计算文本字符串的宽度
        float textWidth = mPaint.measureText(text);

        //在控件中心绘制文本字符串
        canvas.drawText(text, px - textWidth / 2, py, mPaint);
    }

    /**
     * 调整控件大小
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     * 对参数解码,可以计算出适合的高宽值
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int measuredWidth = measureWidth(widthMeasureSpec);
        int measuredHeight = measureHeight(heightMeasureSpec);

        //必须调用setMeasuredDimension,否则在布局控件的时候,会造成运行时异常
        setMeasuredDimension(measuredWidth, measuredHeight);
    }

    /**
     * 返回计算的组建的宽度
     * @param measureSpec
     * @return
     */
    private int measureWidth(int measureSpec) {
        int sepcMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        //如果不指定限制,就是默认大小
        int result = 500;
        if (sepcMode == MeasureSpec.AT_MOST) {
            //计算控件在这个最大尺寸范围内的理想大小,如果控件填充了可用空间,则返回外边界
            result = specSize;
        } else if (sepcMode == MeasureSpec.EXACTLY) {
            //如果控件可以放置在这个边界内,则返回该值
            result = specSize;
        }
        return result;
    }

    /**
     * 返回计算的组建的高度
     * @param measureSpec
     * @return
     */
    private int measureHeight(int measureSpec) {
        int sepcMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        //如果不指定限制,就是默认大小
        int result = 500;
        if (sepcMode == MeasureSpec.AT_MOST) {
            //计算控件在这个最大尺寸范围内的理想大小,如果控件填充了可用空间,则返回外边界
            result = specSize;
        } else if (sepcMode == MeasureSpec.EXACTLY) {
            //如果控件可以放置在这个边界内,则返回该值
            result = specSize;
        }
        return result;
    }
}
1.4 attrs.xml属性全解: http://blog.csdn.net/aldridge1/article/details/14005403
1.5 attr属性format讲解: http://www.cnblogs.com/rayray/p/3442026.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值