BobGo

Android系统编程入门系列之界面Activity绘制展示

上篇文章介绍了界面Activity的启动方式和生命周期,本篇将继续介绍在界面Activity中的内容是如何绘制展示给用户的。

在Android系统上运行新创建的界面Activtiy,给用户展示的是空白的。而得益于AndroidStudio的强大模板支持,新创建的界面Activity会自动重写onCreate()方法,并在该方法内自动创建以下两行类似默认代码。

super.onCreate(savedInstanceState)
setContentView(R.layout.xxx)

显然,setContentView()方法就是加载当前界面Activity的绘制内容,而名为R.layout.xxx的参数中的xxx,代指文件名,被称为布局文件。在系列文章首篇有提到res目录是存放应用资源的文件夹,该目录下有个名为layout的目录,就是专门存放布局文件的。
在AndroidStudio中,只要在layout目录下创建布局文件,就可以在java目录下源代码中通过调用R.layout中与布局文件同名的常量来使用,而其中的类名为R的文件,是在java(generate)目录的当前应用包名下自动生成的。

在AndroidStudio中新创建的布局文件默认生成如下模板,该文件符合xml文件格式,其中标签名只能是Android系统定义或我们应用内自定义的控件名,而其属性也只能使用已定义的属性。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    tools:context=".MainActivity">

</android.support.constraint.ConstraintLayout>

视图

View视图,是Android系统绘制图形的基本单元类,也是系统与用户交互的基本单元类。所有的视图类追踪其父类,都可以追溯到同一个父类android.view.View

如果直接在布局文件中添加视图使用,那么称这个视图是静态加载的。静态创建视图需要使用大量布局文件,在应用程序打包时占用大量存储空间,但是当应用程序安装之后,Android系统会更加快速的绘制这些视图。
相反地,也可以在源代码文件的当前界面Activity中,直接调用视图类的构造方法创建一个视图,并加载到当前界面中,那么称该视图是动态加载的。动态创建的视图是写入到源代码中,所以应用程序打包时不会占用太多存储空间,只是在应用程序安装运行后,Android系统需要更多时间去解析并绘制这些视图。
上面两种视图加载方式虽然有所差别,但是在不涉及大量视图加载的轻量级应用中差别不是很大。一般界面中的视图都是固定数量的,可以直接静态创建视图的,这样可以借助AndroidStudio的布局预览插件实时预览当前界面布局效果;只有在根据不同数据加载不同视图这种类似的动态改变大量界面时,可能动态加载视图的方式会更方便一些。

视图除了需要创建加载外,还可以修改其属性以便于Android系统区分绘制,同样地,既可以在静态加载时动态设置默认属性值,也可以在源代码中动态设置属性值。

注意:针对同一个属性,动态设置结果会优先覆盖静态属性值。

任何视图在创建加载后,有两个属性值是必须要指定的,那就是表示视图宽度和高度的尺寸,在布局文件中,静态加载的视图必须以标签中的android:layout_widthandroid:layout_height属性设置;在源代码中,动态加载的视图默认宽度和高度都是LayoutParams.WRAP_CONTENT,表示自适应内容尺寸,也可以调用视图类中的.layoutParams.width.layoutParams.height变量直接访问或修改。

根据视图负责的任务不同,可大致分为容器类视图、展示类视图、编辑类视图三种类型。

  1. 容器类视图,一般符合后缀为Layout的命名规范,可作为父视图,在其内部继续加载绘制子视图,包括展示类视图和编辑类视图。常用的有将子视图位置线性排列绘制的线性布局android.widget.LinearLayout,对子视图使用相对位置绘制的相对布局android.widget.RelativeLayout,集合上述两种布局优势更加灵活绘制子视图相对位置的约束布局androidx.constraintlayout.widget.ConstraintLayout等。
  2. 展示类视图,可理解为输出型视图,一般是容器类视图的子类,且符合后缀为View的命名规范,主要负责将数据资源以更直观的形式展示给用户。像是展示文本内容的android.widget.TextView,展示图片的android.widget.ImageView,展示视频的android.widget.VideoView等。
  3. 编辑类视图,可理解为输入型视图,一般是展示类视图或容器类视图的子类,且命名都不符合上述两种类型的命名规范,可以根据用户的控制实时改变自身视图的相关属性。比如允许用户文字输入并实时展示输入结果的android.widget.EditText,允许用户选择时间并展示的android.widget.TimePicker,在用户操作需要提示时展示的android.widget.Toast等。

系统视图

正如上边举例的几个视图,都是在AndroidSDK中定义的视图,称为系统视图。系统视图都位于android.widget包下。
在日常开发中,只需要掌握如何使用系统视图即可。这里以动态加载的方式介绍几个常用视图案例。

首先是界面Activity对应的布局文件,以上文中AndroidStudio自动生成的模板布局文件为例,为了在源代码中获取到这个布局文件的根视图,在根视图<android.support.constraint.ConstraintLayout>标签中增加android:id 属性,属性值以@+id/开头表示新增属性值,可以定义任意值;而如果以@id/开头,只能使用已经定义过的属性值。所有新增的属性,都由AndroidStudio自动存储在前面提到的R文件中,在源代码中可以通过R.id.xxx常量获取,其中xxx即定义的属性值。

android:id="@+id/activity_main_root"

之后便可以在源代码的界面Activity中获取到该视图,并操作该视图例如增加子视图。

        val rootLayout = findViewById<ConstraintLayout>(R.id.activity_main_root)

        val button1=Button(this)
        button1.text="按钮"
        button1.id=R.id.button_1

        val textView1=TextView(this)
        textView1.text="文本展示区"

        val layoutParamsButton1 = ConstraintLayout.LayoutParams(
            ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        )
        layoutParamsButton1.topMargin = 500
        layoutParamsButton1.topToTop=rootLayout.id
        layoutParamsButton1.leftToLeft=rootLayout.id
        layoutParamsButton1.rightToRight=rootLayout.id
        button1.layoutParams=layoutParamsButton1

        val layoutParamsTextView1 = ConstraintLayout.LayoutParams(
            ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        )
        layoutParamsTextView1.circleConstraint=button1.id
        layoutParamsTextView1.circleAngle=30f
        layoutParamsTextView1.circleRadius=300
        textView1.layoutParams=layoutParamsTextView1


        rootLayout.addView(button1)
        rootLayout.addView(textView1)

在上边代码中有用到R.id.button1变量,由于其绑定视图并未在布局文件中静态加载,所以该视图对应的id值只能在资源文件中单独声明,可以在res资源目录下新建一个xml格式的资源文件,在其中增加如下代码以创建一个id供上边源代码中使用。

<resources>
    <item name="button_1" type="id"></item>
</resources>

最终上述代码的运行效果如下图。
image

自定义视图

虽然AndroidSDK提供的系统视图可以满足大部分常见场景,但是开发的乐趣在于自由绘制视图及交互。Android系统同样允许开发者自由定义视图以满足他们的特殊爱好需求。想必不需强调也能理解,这种自定义视图只能在定义该视图的所属modle模块中使用,或者在定义了自定义视图的所属modle模块的被依赖子modle模块中使用。对于自定义视图,其相关属性可能因视图而异,但是使用方式与系统视图类似,所以主要掌握如何定义自定义视图。这里以自定义的输入框为例简单说明。

自定义视图首先要在源代码中定义一个视图类,该类必须继承android.view.View或其子类。在介绍视图分类时可以发现,容器类视图是另外两种视图的父类。所以一般在编辑类视图和展示类视图均不满足自定义视图的需求时,就直接选择容器类视图作为自定义视图的父类,而不是直接继承android.view.View。而如果自定义视图的需求与某个编辑类视图或展示类视图的功能重合,也就可以直接在该视图上修改,那么直接继承这个编辑类视图或展示类视图即可。

在继承视图类之后,Java语言的话,需要重写自定义视图的三个构造方法,而如果应用程序是运行在Android5.0及以上的系统上,还需要重写第四个构造方法。

Android版本号5.0,对应于Android API等级为21,对应Android版本简称为LOLLIPOP

    public MyEditText(Context context) {
		//动态加载视图时调用该构造方法
        this(context, null);
    }

    public MyEditText(Context context, AttributeSet attrs) {
		//静态加载视图时调用该构造方法
        this(context, attrs, 0);
    }

    public MyEditText(Context context, AttributeSet attrs, int defStyleAttr) {
		//确保其他构造方法都最终调用该构造方法
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public MyEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

而如果是Kotlin编写的话,只需要重写两个构造方法即可

	@JvmOverloads
	constructor(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : super(
		context,
		attrs,
		defStyleAttr
	) {
	}

	@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
	constructor(
		context: Context?,
		attrs: AttributeSet?,
		defStyleAttr: Int,
		defStyleRes: Int
	) : super(context, attrs, defStyleAttr, defStyleRes) {
	}

之后可根据需求重写三个父类方法

  1. 视图布局,调用onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int)方法,确保该自定义视图在其父视图的位置。
  2. 视图测量,调用onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int)方法,确定该视图在绘制时的尺寸。
  3. 视图绘制,调用onDraw(canvas: Canvas)方法,将该视图中的属性信息绘制到屏幕上。

如果上述过程都没有问题,就可以像加载系统视图一样的加载自定义视图了。但是自定义视图往往也需要新增一些属性控制,如果简单的在该视图类中增加一些getter、setter方法以访问相关属性,这样就可以在源代码中像使用系统视图一样的动态加载自定义视图了。但是如果想在布局文件中静态加载视图,这就需要重写上述代码中的三参构造方法中了。
静态设置属性值,首先就要先创建固定的属性名,在res/values资源目录中创建xml格式的资源文件,并在其中定义属性值如下所示

    <declare-styleable name="StyleMyEditText">
        <attr name="myDefaultText" format="string" />
    </declare-styleable>

之后在上述重写构造方法中获取这些属性值并赋予其实际意义即可。

		val typedArray = context.obtainStyledAttributes(attrs, R.styleable.StyleMyEditText)
		val defaultText = typedArray.getString(R.styleable.StyleMyEditText_myDefaultText)
		setText(defaultText)
		typedArray.recycle()  //回收代码不能少,否则容易内存泄漏

如此,就可以在静态加载自定义视图的布局文件中正常设置其属性值了。

    <com.kotlin.helloword.MyEditText
        android:layout_width="200dp"
        android:layout_height="100dp"
        app:myDefaultText="默认显示内容"
        />

自定义属性值通过app域使用,android域只能设置Android系统提供的固定属性值

内容有限,视图的简单使用先大致这些,详情案例可参考后续视频内容。而视图作为系统与用户交互的基本单元,其使用功能更加多元,下一章将继续介绍视图间的动画与交互。

相关文章:

猜你喜欢