3.创建并显示视图

作者: Jennyni1122 | 来源:发表于2018-09-05 10:04 被阅读11次

    3.1 问题

    应用程序需要视图元素来显示消息并与用户交互。

    3.2 解决方案

    (API Level1)
    无论是使用Android SDK中的各种视图和小部件,还是创建自定义显示,所有的应用程序都需要使用视图来与用户进行交互。在Android中构建用户界面的首选方法是,在XML中将其定义,然后在运行时调用。
    Android中的视图结构是树状的,根部通常是Activity或窗口的内容视图。ViewGroup是一种特殊的视图,用于管理一个或多个子视图的显示方式。子视图可以是另一个ViewGroup,整颗视图树就这样继续生长。所有的标准布局类都源自ViewGroup,经常作为XML布局文件的根节点。

    3.3 实现机制

    下面定义一个有两个Button实例和一个EditText的布局来接收用户输入。我们可以在res/layout中定义一个名为main.xml的文件,参见以下代码:
    res/layout/main.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:orientation="vertical" >
        
        <EditText
            android:id="@+id/editText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <Button
                android:id="@+id/save"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Save"/>
            <Button
                android:id="@+id/cancel"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" 
                android:text="Cancel"/>
        </LinearLayout>
    </LinearLayout>
    

    Linearlayout是一个ViewGroup,它将元素横向或纵向排列。在main.xml中,EditText和其中的LinearLayout是按序纵向排列的。内部的LinearLayout(里面是按钮)的内容是横向排列的。带有android:id值的视图元素可以在Java代码中引用,以备进一步自定义或显示之用。
    为了用这个布局显示Activity的内容,必须在运行时将其填充。经过重载的Activity.setContentView()方法可以很方便地完成这个工作,只需要提供布局的ID值即可。在Activity中设置布局就是这样简单:

        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
          //继续初始化 Activity
        }
    

    除了提供ID值(main.xml有一个自动生成ID——R.layout.main),不需要其他的内容。如果在将布局附加到窗口之前还需要进一步自定义,可以手动将其填充,在完成所需的自定义后再将其作为内容视图添加。以下代码填充了同一个布局,但在显示之前加上了第三个按钮。
    在显示之前修改布局

        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
               //填充布局文件
            LinearLayout layout = (LinearLayout)getLayoutInflater().inflate(R.layout.main,null);
            //添加一个按钮
            Button reset  = new Button(this);
            reset.setText("Reset Form");
            layout.addView(reset,new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
            //将视图关联到窗口
            setContentView(layout);
    

    在这个示例中,这个XML布局是在Activity的代码中用LayoutInflater填充的,它的inflate()方法会返回一个指向填充后的视图的句柄。因为LayoutInflater.inflate()返回的是视图,所以我们必须将其转换成XML中的某个子类,这样才能在将其关联到窗口之前进行修改。

    注意:
    XML布局文件中的根元素是LayoutInflater.inflate()返回的View元素。

    inflate()的第二个参数代表父ViewGroup,这个参数非常重要,因为它定义了如何解释被填充布局中的LayoutParams。可能的话,只要你知道被填充视图的父视图,就应该把它传进来;否则,XML中根视图的LayoutParams会被忽略。当传入一个父视图后,还要注意inflate()的第三个参数,该参数决定了被填充的布局是否会自动关联到父视图上。在后面的示例中会看到这种机制对于自定义视图是非常有用的。但在本例中,我们填充的是Activity最顶层的视图,因为这里传递了null。

    完全自定义视图

    有时,SDK中的可用小部件不足以提供你所需的输出。或许是要将多个显示元素结合到单个视图中,减少层次结构中视图的数量以提升性能。对于这些情况,就要创建自己的View子类。创建View子类之后,类和框架之间就有两个主要的交互方面需要关注:测量和绘制

    测量
    自定义视图必须满足的第一个要求是向框架提供其内容的测量。在显示视图的层次结构之前,Android会为每个元素(布局和视图节点)调用onMeasure()并向该方法传递两个约束,视图应该使用两个约束来管理如何报告其应该具备的大小。每个约束是一个称为MeasureSpec的封包整数,它包含模式标记和大小值。其中,模式采用如下值之一:

    • AT_MOST : 如果视图的布局参数是match_parent或存在其他的大小上限,则通常使用此模式。该模式告诉视图,其应该报告所需大小,前提是不超出规范中规定的值。
    • EXACTLY : 如果视图的布局参数是固定的,则通过使用此模式。框架期望视图自动设置大小及匹配规范——不多不少。
    • UNSPECIFIED : 该值通常用于指出视图在无约束时所需大小。它可能是另一个具有不同约束的测量的前置模式,或者可能只是因为布局参数被设置为wrap_content且父节点中没有其他约束。在此模式中,视图可能报告其在任何情况下所需的大小。此规范中的大小通常为0。

    完成所报告大小的计算之后,必须在onMeasure()返回之前将这些值传入setMeasuredDimension()调用。如果没有这样做,框架将报告严重的错误。
    通过测量还可以基于可用控件配置视图的输出。测量约束基本上表明在布局内分配了多少空间,因此如果要创建的视图在方向上与其所包含的内容不同,例如垂直空间或多或少,onMeasure()将提供决策所需的信息。
    注意:
    在测量期间,视图实际上还没有确定大小;它只有已测量的尺寸。如果在分配大小后需要对视图做一些自定义工作,则应该重写onSizeChanged()并添加适当的代码。

    绘制
    自定义视图的第二个步骤就是绘制内容,这可能是最重要的步骤。对视图进行测量并将其放置在布局层次结构中之后,框架将为该视图构造一个Canvas示例,调整其大小并放置在适当的位置,然后通过onDraw()传递该实例以供视图使用。Canvas对象驻留单独的绘制调用,因此它包括的drawLine()、drawBitmap()和drawText()等方法用于独立地布局视图内容。如同其名称所暗示的那样,Canvas使用Painter的算法,因此最后绘制的项将放在第一个绘制项的顶部。
    绘制的内容会依附到通过测量和布局提供的视图的边界上,因此虽然可以对Canvas元素进行平移、缩放、旋转等操作,但不能在放置视图的矩形外部绘制内容。
    最后,在onDraw()中提供的内容不包括视图的背景,可以使用setBackgroundColor()或setBackgroundResource()等方法设置该背景。如果在视图上设置背景,则背景会自动绘制,不需要再onDraw()中进行处理。
    以下代码显示了应用程序可以遵循的非常简单的定制视图模版。至于其中的内容,我们绘制了一系列同心圆表示靶心目标。
    自定义视图的示例

    public class BullsEyeView extends View {
    
        private Paint mPaint;
        private Point mCenter;
        private float mRadius;
    
        /*
         * Java构造函数
         */
        public BullsEyeView(Context context) {
            this(context, null);
        }
    
        /*
         * XML构造函数
         */
        public BullsEyeView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        /*
         * 带有样式的XML构造函数 
         */
        public BullsEyeView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            //在此构造函数中进行视图的初始化工作
    
            //创建用于绘制的画刷
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            //我们要绘制填充的圆
            mPaint.setStyle(Style.FILL);
            //创建圆的中心点
            mCenter = new Point();
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int width, height;
            //确定内容的理想大小,无约束
            int contentWidth = 200;
            int contentHeight = 200;
            
            width = getMeasurement(widthMeasureSpec, contentWidth);
            height = getMeasurement(heightMeasureSpec, contentHeight);
            //必须使用测量值调用此方法!
            setMeasuredDimension(width, height);
        }
        
        /*
         *用于测量宽度和高度的辅助方法
         */
        private int getMeasurement(int measureSpec, int contentSize) {
            int specSize = MeasureSpec.getSize(measureSpec);
            switch (MeasureSpec.getMode(measureSpec)) {
                case MeasureSpec.AT_MOST:
                    return Math.min(specSize, contentSize);
                case MeasureSpec.UNSPECIFIED:
                    return contentSize;
                case MeasureSpec.EXACTLY:
                    return specSize;
                default:
                    return 0;
            }
        }
        
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            if (w != oldw || h != oldh) {
                //如果有变化,则复位参数
                mCenter.x = w / 2;
                mCenter.y = h / 2;
                mRadius = Math.min(mCenter.x, mCenter.y);
            }
        }
        
        @Override
        protected void onDraw(Canvas canvas) {
            //绘制一系列从小到大且颜色交替变换的同心圆
            mPaint.setColor(Color.RED);
            canvas.drawCircle(mCenter.x, mCenter.y, mRadius, mPaint);
            
            mPaint.setColor(Color.WHITE);
            canvas.drawCircle(mCenter.x, mCenter.y, mRadius * 0.8f, mPaint);
            
            mPaint.setColor(Color.BLUE);
            canvas.drawCircle(mCenter.x, mCenter.y, mRadius * 0.6F, mPaint);
            
            mPaint.setColor(Color.WHITE);
            canvas.drawCircle(mCenter.x, mCenter.y, mRadius * 0.4F, mPaint);
            
            mPaint.setColor(Color.RED);
            canvas.drawCircle(mCenter.x, mCenter.y, mRadius * 0.2F, mPaint);
        }
    }
    

    首先可以注意到该视图有如下3个构造函数:

    • View(Context context):通过Java代码构造视图时使用该版本。
    • View(Context,AttributeSet):从XML填充视图时使用该版本。AttributeSet包括附加到视图的XML元素的所有属性。
    • View(Context,AttributeSet,int):该版本类似于上一个版本,但在将样式属性添加到XML元素时被调用。

    常用的方案是将所有3个构造函数链接在一起,并且仅在最后一个构造函数中实现定制,这就是我们在视图示例中完成的工作。
    在onMeasure()中,我们使用一种简单的实用方法,基于测量约束返回正确的尺寸。我们基本上可以在所需的内容大小(在此任意选择大小,但应该表示真实应用程序中的视图内容)和所提供的大小之间选择。对于AT_MOST,我们选择两者中较小的值;即视图的大小应该适合我们的内容,前提是不超出规范的大小。完成测量后,我们调用onSizeChanged()收集一些所需的基本数据来绘制目标圆。我们等到此处才调用该方法,这是为了确保使用确切符合视图布局的值。
    在onDraw()内部,我们构造显示内容。在Canvas上绘制5个逐步递减半径且颜色交替交换的同心圆。Paint元素控制所绘制内容样式的相关信息,例如笔画宽度,文本大小和颜色。在为此视图声明Paint时,设置样式为FILL,这就确保使用每种颜色填充圆。根据Painter的算法,在较大圆的顶部绘制较小的圆,这就提供了我们所需的目标效果。
    将此视图添加到XML布局非常简单,但因为视图没有驻留在android.view或android.widget包中,我们需要使用类的完全限定包名命名元素。例如,如果应用程序包是com.androidrecipes.customwidgets,则XML代码如下所示:

        <com.examples.customwidgets.BullsEyeView
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    

    如图显示了将此视图添加到Activity的结果。


    靶心定制视图

    相关文章

      网友评论

        本文标题:3.创建并显示视图

        本文链接:https://www.haomeiwen.com/subject/flxbwftx.html