android studio for android learning (二十六 )自定义控件理解与浅析(1)

版权声明:原创文章,未经博主允许不得转载,欢迎加入AR/VR开发群:548077040。 https://blog.csdn.net/yywan1314520/article/details/52737707

1.自定义控件

刚开始以为自定义控件很容易,后来发现涉及的内容太多了,不是一次能学清楚讲明白的,这里本人结合自己的学习经验,简单的介绍下如何自定义一些简单的控件,并给出相应的代码,供大家学习,如有错误欢迎大家评判指正,如有意见可以在下面留言。

2.切入正题,如何自定义控件,首先得要明白和了解view

任何复杂的技术后面都是一点点简单知识的积累。通过对自定义控件的学习去可以更深入的掌握android的相关知识点。在Android APP中,所有的用户界面元素都是由View和ViewGroup的对象构成的。View是绘制在屏幕上的用户能与之交互的一个对象。而ViewGroup则是一个用于存放其他View(和ViewGroup)对象的布局容器! Android为我们提供了一个View和ViewGroup子类的集合,集合中提供了一些常用的输入控件(比如按钮和文本域)和各种各样的布局模式(比如线性或相对布局)

这里写图片描述

官方说法:Android里的图形界面都是由View和ViewGroup以及他们的子类构成的: View:所有可视化控件的父类,提供组件描绘和时间处理方法 。ViewGroup: View类的子类,可以拥有子控件,可以看作是容器 Android UI中的控件都是按照这种层次树的结构堆叠得,而创建UI布局的方式有两种, 自己在Java里写代码或者通过XML定义布局,后者显得更加方便和容易理解! 也是我们最常用的手段!另外我们一般很少直接用View和ViewGroup来写布局,更多的时候使用它们的子类控件或容器来构建布局!

3.自定义View的基本步骤,有些可能不需要,大家根据自己的实际情况来选择:

1、创建View
2、处理View的布局
3、绘制View
4、与用户进行交互
5、优化已定义的View

也有人说,自定义view的时候,其实很简单,只需要知道3步骤:测量——onMeasure():决定View的大小,布局——onLayout():决定View在ViewGroup中的位置,绘制——onDraw():如何绘制这个View,这个因人而异,个人觉得怎么理解都可以。下面按5个步骤来讲解自定义控件的步骤。

4、创建MyView

这一步当然是继承View了,public class MyView extends View,android的控件基本都是继承View的,如果你是想用某些控件的一些功能,你可以单独继承这个控件,如button,TextView等

为了能在.xml文件中编辑了一个控件,在运行的时候就能够看到和获得这个控件。我们自定义的控件当然也要支持配置和一些自定义属性,所以下面的构造方法就必须有了 super(context, attrs, defStyle)。这个构造方法允许我们在.xml文件中创建和编辑我们自定义控件的实例。

public MyView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        ...
        ...

    }

定义自定义属性

大部分情况我们的自定义View需要有更多的灵活性,比如我们在xml中指定了颜色大小等属性,在程序运行时候控件就能展示出相应的颜色和大小。所以我们需要自定义属性,自定义属性通常写在在res/values/attrs.xml文件中,如下,name=”CustomTitleView”,就和我们平时用的控件是一样的,名字确定后就可以用了,下面我们主要是定义了一个两行的文本框,分为标题和内容,其中标题和内容可以获取和改变,也能改变标题和背景的颜色,同时要圆角显示,这里还设置了字体的颜色和尺寸.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="titleText" format="string" />
    <attr name="content" format="string"/>
    <attr name="titleTextColor" format="color" />
    <attr name="titleTextSize" format="dimension" />

    <declare-styleable name="CustomTitleView">
        <attr name="titleText" />
        <attr name="content"/>
        <attr name="titleTextColor" />
        <attr name="titleTextSize" />
    </declare-styleable>
</resources>

上面自定义好的控件如何使用呢?在我们的主main.xml中,可以按下面方式使用,本人用的是最新版本的AS IDE,注意这里,使用自定义属性的时候需要指定命名空间,固定写法就是xmlns:custom=”http://schemas.android.com/apk/res-auto”

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:custom="http://schemas.android.com/apk/res-auto"
                android:layout_width="match_parent"
                android:layout_height="match_parent" >

    <com.example.dragon.defineyouwidget.MyView
        android:layout_width="100dp"
        android:layout_height="50dp"
        custom:titleText="美食广场"
        custom:content="800M"
        custom:titleTextColor="#ff0000"
        custom:titleTextSize="18sp"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="135dp"/>

</RelativeLayout>

获取自定义属性在xml中设置了控件自定义属性,我们就需要拿到属性做一些事情。否则定义自定义属性就没有意义了。固定的获取自定义属性代码如下

TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
        int n = a.getIndexCount();
        for (int i = 0; i < n; i++)
        {
            int attr = a.getIndex(i);
            switch (attr)
            {
                case R.styleable.CustomTitleView_titleText:
                    mTitleText = a.getString(attr);
                    break;
                case R.styleable.CustomTitleView_content:
                    mContent = a.getString(attr);
                    break;
                case R.styleable.CustomTitleView_titleTextColor:
                    // 默认颜色设置为黑色
                    mTitleTextColor = a.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.CustomTitleView_titleTextSize:
                    // 默认设置为16sp,TypeValue也可以把sp转化为px
                    mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
                            TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
                    break;

            }

        }
a.recycle();

当我们在 xml中创建了一个view时,所有在xml中声明的属性都会被传入到view的构造方法中的AttributeSet类型的参数当中。通过调用Context的obtainStyledAttributes()方法返回一个TypedArray对象。然后直接用TypedArray对象获取自定义属性的值。由于TypedArray对象是共享的资源,所以在获取完值之后必须要调用recycle()方法来回收。

5、处理View的布局

一个View是在展示时总是有它的宽和高,测量View就是为了能够让自定义的控件能够根据各种不同的情况以合适的宽高去展示。提到测量就必须要提到onMeasure方法了。onMeasure方法是一个view确定它的宽高的地方。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

下面部分本文未用到,作为补充知识:

onMeasure方法里有两个重要的参数, widthMeasureSpec, heightMeasureSpec。在这里你只需要记住它们包含了两个信息:mode和size
我们可以通过以下代码拿到mode和size

int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
那么获取到的mode和size又代表了什么呢?
mode代表了我们当前控件的父控件告诉我们控件,你应该按怎样的方式来布局。
mode有三个可选值:EXACTLY, AT_MOST, UNSPECIFIED。它们的含义是:

EXACTLY:父控件告诉我们子控件了一个确定的大小,你就按这个大小来布局。比如我们指定了确定的dp值和macth_parent的情况。
AT_MOST:当前控件不能超过一个固定的最大值,一般是wrap_content的情况。
UNSPECIFIED:当前控件没有限制,要多大就有多大,这种情况很少出现。

size其实就是父布局传递过来的一个大小,父布局希望当前布局的大小。

下面是一个重写onMeasure的固定伪代码写法:

if mode is EXACTLY{
父布局已经告诉了我们当前布局应该是多大的宽高, 所以我们直接返回从measureSpec中获取到的size
}else{
计算出希望的desiredSize
if mode is AT_MOST
返回desireSize和specSize当中的最小值
else:
返回计算出的desireSize
}
上面的代码虽然基本都是固定的,但是需要写的步骤还是有点多,如果你不想自己写,你也可以用android为我们提供的工具方法:resolveSizeAndState,该方法需要传入两个参数:我们测量的大小和父布局希望的大小,它会返回根据各种情况返回正确的大小。这样我们就可以不需要实现上面的模版,只需要计算出想要的大小然后调用resolveSizeAndState。之后在做自定义View的时候我会展示用这个方法来确定view的大小。

计算出height和width之后在onMeasure中别忘记调用setMeasuredDimension()方法。否则会出现运行时异常。

计算一些自定义控件需要的值 onSizeChange()

onSizeChange() 方法在view第一次被指定了大小值、或者view的大小发生改变时会被调用。所以一般用来计算一些位置和与view的size有关的值。


6、绘制View(Draw)

onDraw()简单来说就两点:Canvas决定要去画什么?Paint决定怎么画?

比如,Canvas提供了画线方法,Paint就来决定线的颜色。Canvas提供了画矩形,Paint又可以决定让矩形是空心还是实心

在onDraw方法中开始绘制之前,你应该让画笔Paint对象的信息初始化完毕。这是因为View的重新绘制是比较频繁的,这就可能多次调用onDraw,所以初始化的代码不应该放在onDraw方法里,下面是画笔的一些初始化方法。

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(mTitleTextSize);
        mPaint.setTextAlign(Paint.Align.CENTER);
        // mPaint.setColor(mTitleTextColor);

7、与用户进行交互

也许某些情况你的自定义控件不仅仅只是展示一个漂亮的内容,还需要支持用户点击,拖动等等操作,这时候我们的自定义控件就需要做用户交互这一步骤了。

在android系统中最常见的事件就是触摸事件了,它会调用view的onTouchEvent(android.view.MotionEvent).重写这个方法去处理我们的事件逻辑

  @Override
   public boolean onTouchEvent(MotionEvent event) {
    return super.onTouchEvent(event);
   }

对与onTouchEvent方法相信大家都有一定了解,如果不了解的话,你就先记住这是处理Touch的地方。

现在的触控有了更多的手势,比如轻点,快速滑动等等,所以在支持特殊用户交互的时候你需要用到android提供的GestureDetector.你只需要实现GestureDetector中相对应的接口,并且处理相应的回调方法。

除了手势之外,如果有移动之类的情况我们还需要让滑动的动画显示得比较平滑。动画应该是平滑的开始和结束,而不是突然消失突然开始。在这种情况下,我们需要用到属性动画 property animation framework

8、优化

其实一个完善的自定义控件已经出来了。接下来你要做的只是确保自定义控件运行得流畅

1、避免不必要的代码
2、在onDraw()方法中不应该有会导致垃圾回收的代码。
3、尽可能少让onDraw()方法调用,大多数onDraw()方法调用都是手动调用了invalidate()的结果,所以如果不是必须,不要调用invalidate()方法。

到此介绍完了一个简单的创建空间的步骤,基本流程图如下:

这里写图片描述

9、实例,完成后的效果图如下,github源码下载DefineYouWidget

这里写图片描述


10.Reference

1.http://www.cnblogs.com/0616–ataozhijia/p/4003380.html

2.http://www.open-open.com/lib/view/open1460459070076.html

3.http://blog.csdn.net/lmj623565791/article/details/24252901/

4.http://blog.csdn.net/hudashi/article/details/50913257

5.http://blog.csdn.net/hudashi/article/details/50905325

6.http://www.zhihu.com/question/41101031

7.http://www.runoob.com/w3cnote/android-tutorial-view-viewgroup-intro.html

专注于AR的在移动端的实现,如果你还有问题没解决,你可以加入我们一起交流。或是关注我们的技术公众号,这是提供技术干货的地方,你有干货可以向我们推荐。

这里写图片描述

如果你觉得写的不错,对你有用,请在下面点个 支持一下,有问题可以在下面留言评论。


猜你喜欢

转载自blog.csdn.net/yywan1314520/article/details/52737707