android自定义View全解

源码在这里:CSDN

本文主要有以下内容:

* 自定义View的分类

* 自定义View的注意事项

* 自定义View的实现

* 自定义View使其支持wrap_content和padding

* 自定义属性的实现过程

首先,自定义View是为了达到更绚丽的效果。它相对来说也是一个比较难的技术体系,涉及到【View的层次结构】、【View事件分发机制】、【View的工作原理】、【View的弹性滑动】、【View的滑动冲突】等技术。

自定义View的分类

1.1. 继承 View重写 onDraw 方法

这种方式主要用于显示不规则的效果哦,即这种效果不方便用布局组合来实现,往往需要静态或者动态的显示一些不规则的图形

采用这种方式需要自己支持wrap_content,并且padding也需要自己处理。

1.2. 继承ViewGroup派生特殊的Layout

这种方法主要用于实现自定义布局,即除了LinearLayout,RelativeLayout等系统布局之外的一种重新定义的全新的布局,当某种效果很像

几种View组合在一起的时候就可以采用这种方法。

这种方法稍微复杂一些,需要合适的处理ViewGroup的测量和布局这俩个过程

1.3. 继承特定的View(比如TextView)

这种方法一般用于扩展某种已有的View功能。这种方法不需要自己支持wrap_content,padding等。

1.4. 继承特定的ViewGroup(比如LinearLayout等)

当某种效果很像几种View组合在一起的时候就可以采用这种方法。这种方法不需要自己处理ViewGroup的测量和布局这俩个过程。

与1.2比较,一般来说,1.2能实现的效果1.4也都能实现,但1.2更接近View的底层。

自定义View的注意事项

2.1. 让View支持wrap_content

这是因为直接继承View或ViewGroup的控件,如果不在onMeasure中处理wrap_content,那么外界在布局中使用wrap_content时就无法达到预期效果

2.2. 让View支持padding

直接继承View的控件,如果不再draw方法中处理padding,那么这个属性是无法起作用的。

直接继承ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,不然将导致pading和子元素的margin失效

2.3. 不要在View中使用Handler

这是因为View内部本身就提供了post系列方法,完全可以替代Handler的作用。除非你很明确要用Handler来发送消息。

2.4. View中如果有线程和动画,及时停止

如果有线程和动画需要停止的时候,onDetachedFromWindow就恶意做到。这是因为当包含此View的Activity退出或者当前View被remove时,View的onDetachedFromWindow方法就会被调用。相对的,当包含此View的Activity启动时onAttachedToWindow会被调用。同时,View不可见时,我们也需要停止线程和动画,如果不及时停止,可能会导致内存泄漏。

2.5. 如果有滑动嵌套时,当然要处理好滑动冲突的问题。

================================================

继承View重写 onDraw 方法

3.1 代码
public class CircleView extends View {
    private int mColor = Color.RED;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    public CircleView(Context context) {
        this(context, null);
    }
    public CircleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
       super(context, attrs, defStyleAttr);
        mPaint.setColor(mColor);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        int radius = Math.min(width, height) / 2;
        canvas.drawCircle(width / 2, height / 2, radius, mPaint);
    }
}

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.peak.app.c_view.CustomViewMainActivity">
    <com.peak.app.c_view.CircleView
        android:layout_width="120dip"
        android:layout_height="120dip"
        android:padding="40dip"
        android:background="@color/colorPrimaryDark" />
</LinearLayout>

上述代码就完成了基本的自定义View,同样上述1.1,2.2,2.2中提到过,我们需要自己实现使其支持wrap_content和padding,现在我们就用上述代码来验证当前自定义view是否支持wrap_content和padding

3.2 代码验证当前自定义View是否支持wrap_content和padding

图一:证明支持layout_margin属性

图二:证明不支持padding属性

图三、图五:证明不支持wrap_content

layout_margin属性是由父容器控制的,所以支持,不需要我们处理

3.3让View支持wrap_content

之所以wrap_content失效,是因为:如果View在使用wrap_content时,那么他的specMode是AT_MOST模式,这种模式下它的宽高等于specSize,而这种情况下specSize是parentSize,而parentSize是父容器当前剩余的大小。

解决方法就是:我们为View指定一个默认的宽高,并在wrap_content时使用此宽高即可。当然这个默认值如何取舍,没有一个固定的依据,我们只能根据需求自行选取一个合适的值。下面的代码是通过Math.min函数来设置默认值的,道理一样。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int mWidth=600;
        int mHeight =600;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if(widthMode==MeasureSpec.AT_MOST && heightMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(Math.min(mWidth, widthSize),Math.min(mHeight, heightSize));
        }else if(widthMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(Math.min(mWidth, widthSize),heightSize);
        }else if( heightMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSize,Math.min(mWidth, widthSize));
        }
    }

测试修改后的View:

        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

3.4让View支持padding属性。

这个只需要在绘制的时候把padding考虑进去即可。

首先在onDraw方法中通过

int width = getWidth() - (paddingLeft + paddingRight);
int height = getHeight() - (paddingTop + paddingBottom);

重新计算圆的实际宽高,

然后,通过

canvas.drawCircle(paddingLeft+width / 2, paddingTop+height / 2, radius, mPaint);

重新定义圆的圆点,使其四周留下空白(也就是padding)

全部代码如下:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        int width = getWidth() - (paddingLeft + paddingRight);
        int height = getHeight() - (paddingTop + paddingBottom);
        int radius = Math.min(width, height) / 2;
        canvas.drawCircle(paddingLeft+width / 2, paddingTop+height / 2, radius, mPaint);
    }

    <com.peak.app.c_view.CircleView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimaryDark"
        android:padding="10dip"/>

继承特定的ViewGroup(比如LinearLayout等)

这种方式主要用于实现自定义布局,实现起来也比较简单,直接上代码:

ActionBarView.java:
public class ActionBarView extends LinearLayout {
    public static final int KEY_BACK = 111;
    public static final int KEY_CONTENT = 222;
    private Context context = null;
    private ImageView iv_back;
    private TextView tv_context;
    private OnClickListener onClickListener = null;
    public ActionBarView(Context context) {
        this(context, null);
    }
    public ActionBarView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        View contentView = LayoutInflater.from(context).inflate(R.layout.actionbar_layout, this);
        iv_back = (ImageView) contentView.findViewById(R.id.iv_back);
        tv_context = (TextView) contentView.findViewById(R.id.tv_context);
        iv_back.setOnClickListener(new MyOnClickListener(KEY_BACK));
        tv_context.setOnClickListener(new MyOnClickListener(KEY_CONTENT));
    }
    // 通过这个方法来动态设置标题
    public void setContent(String str) {
        tv_context.setText(str);
    }
    // 通过回调接口在外部监听点击事件
    private class MyOnClickListener implements android.view.View.OnClickListener {
        private int key = -1;
        private MyOnClickListener(int position) {
            this.key = position;
        }
        @Override
        public void onClick(View v) {
            if (onClickListener != null) {
                onClickListener.onClick(key, v);
            }
        }
    }
    public void setOnClickListener(OnClickListener onClickListener) {
        this.onClickListener = onClickListener;
    }
    public interface OnClickListener {
        public void onClick(int key, View v);
    }
}

actionbar_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="44dip"
    android:background="@drawable/bg_top">
    <ImageView
        android:id="@+id/iv_back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_top_return" />
    <TextView
        android:id="@+id/tv_context"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="center_horizontal"
        android:gravity="center"
        android:text="标题" />
</FrameLayout>

activity的xml文件中引用:
    <com.peak.app.c_view.ActionBarView
        android:id="@+id/barView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dip"
        android:padding="20dip"
        android:background="@color/colorPrimary"/>

activity中:
        ActionBarView barView = (ActionBarView) findViewById(R.id.barView);
        barView.setContent("自定义View标题");
        barView.setOnClickListener(new ActionBarView.OnClickListener() {
            @Override
            public void onClick(int key, View v) {
                if (key == ActionBarView.KEY_BACK) {
                    Log.i("CustomViewMainActivity", "ActionBarView 点击返回按钮");
                }
                if (key == ActionBarView.KEY_CONTENT) {
                    Log.i("CustomViewMainActivity", "ActionBarView 点击标题");
                }
            }
        });

图中,蓝色区域是整个ActionBarView 所占的区域,蓝色区域外是的空白是父布局设置的padding,蓝色区域和其围着的白色区域之间的距离是ActionBarView 设置的padding。所以,这种方式会自动支持padding属性,不需要我们自己处理。

代码中通过public void setContent(String str)方法来动态设置标题,其实我们还可以在xml中来设置,不过这需要我们自定义属性,这个稍后来说。

继承ViewGroup派生特殊的Layout

这种方式使用起来比较复杂,而且也不是很常用,这里就先不说了。

继承特定的View

这里我们继承EditText,实现全部删除的功能,【源码见附件】

自定义属性

有时候,业务需要,或者是单纯的为了使用方便,我们需要自定义属性,使其可以在xml中快速配置属性。比如CircleView中我们可以配置背景色的属性,ActionBarView我们可以配置标题属性。

这里我们以CircleView为例(ActionBarView的自定义属性见【源码】,这里就不往出贴了):

第一步:在res/values/下创建atts开头的自定义属性xml,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleView">
        <attr name="circle_color" format="color" />
    </declare-styleable>
</resources>

上述代码首先定义了一个名称为CircleView的属性集合,当然,既然是集合,就可以有多个属性。这里的属性名是circle_color,格式是color

第二步:在View的构造方法中解析自定义属性的值并做处理,代码如下:

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
//        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleView, defStyleAttr, 0);
//        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView, defStyleAttr, 0);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mColor = a.getColor(R.styleable.CircleView_circle_color, Color.YELLOW);
        a.recycle();
        mPaint.setColor(mColor);
    }

**第三步:在xml布局中使用自定义属性,代码如下:

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.peak.app.c_view.CustomViewMainActivity">
    <com.peak.app.c_view.CircleView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimaryDark"
        android:padding="10dip"
        app:circle_color="@color/colorAccent" />
</LinearLayout>

这里需要注意:必须在布局文件中添加schemas声明:

xmlns:app="http://schemas.android.com/apk/res-auto"。

其中,app是自定义属性的前缀,与下边的app:circle_color="@color/colorAccent"呼应。当然这个前缀是可以改的,是可以自定义的。

另外自定义属性有如下格式:

  1. reference:指资源ID
  2. color:颜色值。
  3. boolean:布尔值
  4. dimension:尺寸值。
  5. float:浮点值。
  6. integer:整型值
  7. string:字符串。
  8. fraction:百分数
  9. enum:枚举值
  10. flag:位或运算
  11. 多类型。

更多内容请关注 我的简书专题

转载请注明 原文链接: http://blog.csdn.net/jasonpeak/article/details/51729192

时间: 2024-11-05 18:11:38

android自定义View全解的相关文章

Android 自定义 View 详解

View 的绘制系列文章: Android View 绘制流程之 DecorView 与 ViewRootImpl Android View 的绘制流程之 Measure 过程详解 (一) Android View 的绘制流程之 Layout 和 Draw 过程详解 (二) Android View 的事件分发原理解析 对于 Android 开发者来说,原生控件往往无法满足要求,需要开发者自定义一些控件,因此,需要去了解自定义 view 的实现原理.这样即使碰到需要自定义控件的时候,也可以游刃有

Android自定义view详解

从继承开始 懂点面向对象语言知识的都知道:封装,继承和多态,这是面向对象的三个基本特征,所以在自定义View的时候,最简单的方法就是继承现有的View 通过上面这段代码,我定义了一个SketchView,继承自View对象,并且复写了它的三个构造方法,我主要来分析一下这三个构造方法: 第一个构造方法就是我们普通在代码中新建一个view用到的方法,例如 SketchView sketchView = new SketchView(this); 就这样,一个自定义的view就被新建出来了,然后可以根

Android自定义View【实战教程】5??---Canvas详解及代码绘制安卓机器人

友情链接: Canvas API Android自定义View[实战教程]3??--Paint类.Path类以及PathEffect类详解 神马是Canvas 基本概念 Canvas:可以理解为是一个为我们提供了各种工具的画布,我们可以在上面尽情的绘制(旋转,平移,缩放等等).可以理解为系统分配给我们一个一个内存空间,然后提供了一些对这个内存空间操作的方法(API), 实际存储是在下面的bitmap. 两种画布 这里canvas可以绘制两种类型的画图,分别是view和surfaceView. V

Android学习之自定义view详解

本文和大家分享的主要是android自定义view相关内容,一起来看看吧,希望对大家学习android有所帮助. 1.自定义View的属性 在 res/values/ 下建立一个 attrs.xml ,在里面定义属性和声明整个样式. <?xml version="1.0" encoding="utf-8"?> <resources> <attr name="titleText" format="string

Android自定义view教程05--自定义view的基础知识之LayoutInflater详解

前面的教程我们讲了一下android的动画 的使用,尤其是着重讲解了属性动画的使用.那么这章开始我们将会讲一下android 自定义view的一些基础知识.帮助大家理解. 首先我们来关注一下LayoutInflater这个类,经常使用开源控件的人对这个类应该很熟悉了,但是很少有人能把这个类讲明白 用清楚,今天我们就来深挖一下这个类. 首先我们定义一个button.xml 和button2.xml 1 <?xml version="1.0" encoding="utf-8

Android 自定义View合集

自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/Mr-XiaoLiang 自定义控件三部曲 http://blog.csdn.net/harvic880925?viewmode=contents Android 从0开始自定义控件之View基础知识与概念 http://blog.csdn.net/airsaid/article/details/5

android自定义View之NotePad出鞘记

现在我们的手机上基本都会有一个记事本,用起来倒也还算方便,记事本这种东东,如果我想要自己实现,该怎么做呢?今天我们就通过自定义View的方式来自定义一个记事本.OK,废话不多说,先来看看效果图. 整个页面还是很简单的. 1.自定义View的分类 OK,那么在正文开始之前,我想先来说说自定义View的分类,自定义View我们一共分为三类 1.自绘控件 自绘控件就是我们自定义View继承自已有控件,然后扩展其功能,之前两篇自定义View的博客(android自定义View之钟表诞生记,android

Android自定义View(RollWeekView-炫酷的星期日期选择控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/53420889 本文出自:[openXu的博客] 目录: 1分析 2定义控件布局 3定义CustomWeekView 4重写onMeasure 5点击后执行动画 7重置预备控件 源码下载 ??最近收到一个自定义控件的需求,需要做一个日期选择控件,实现图如下: ???? ??一次展示一个星期的5天,中间放大的为当前选中的:如果点击了其中一个日期,比如星期五,那么整体向左滑动,并将星期五慢慢放大

Android自定义View——自定义搜索框(SearchView)

概述 在Android开发中,当系统数据项比较多时,常常会在app添加搜索功能,方便用户能快速获得需要的数据.搜索栏对于我们并不陌生,在许多app都能见到它,比如豌豆荚 在某些情况下,我们希望我们的自动补全信息可以不只是纯文本,还可以像豌豆荚这样,能显示相应的图片和其他数据信息,因此Android给我们提供的AutoCompleteTextView往往就不够用,在大多情况下我们都需要自己去实现搜索框. 分析 根据上面这张图,简单分析一下自定义搜索框的结构与功能,有 1. 搜索界面大致由三部门组成