Android学习羁绊之UI设计

原文链接:Android学习羁绊–>UI设计

软件开发过程中,界面设计和功能开发同样重要。Android中有多种编写程序界面的方式可供选择。接下来学习一下Android的UI开发。

常见UI控件

TextView

TextView是Android中最简单的一个控件,它主要用于在界面上显示一段文本信息。<TextView>标签有以下这些属性:

  • andriod:id :给当前控件定义一个唯一标识符。
  • android:layout_width :指定控件的宽度。
    android:layout_height :指定控件的高度。
    Android中所有控件都有这两个属性,有三个可选值:
  • match_parent:表示让当前控件的大小和父布局的大小一样,也就是由父布局来决定当前控件的大小。
  • fill_parent:与match_parent意义相同,官方推荐使用match_parent。
  • wrap_content:表示让当前控件的大小能够刚好包含住里面的内容,也就是由控件内容决定当前控件的大小。
  • android:text :指定TextView中显示的文本内容。
  • android:gravity :指定文字的对齐方式,可选值有top 、bottom 、left、right 、center 等,可以用“|”来同时指定多个值。
  • android:textSize :指定文字大小。Android中字体大小使用sp作为单位。
  • android:textColor :指定文字的颜色。
  • android:layout_gravity :用于指定控件在布局中的对齐方式,可选值和android:gravity差不多
  • android:background :为布局或控件指定一个背景,可以使用颜色或图片来进行填充
  • android:layout_margin :指定控件在上下左右方向上偏移的距离

以上属性都是所有控件都拥有的。

Button

Button(按钮)用于与用户进行交互,<Button>标签中除了共有属性,还有下面这个属性:

  • android:textAllCaps:指定系统是否将Button中的英文字母自动进行大写转换,默认为true。

在界面上定义了一个Button控件还不够,还需要将Button的点击事件注册一个监听器,如下代码所示:

Button button = (Button) findViewById(R.id.bottom);
button.setOnClickListener(new View.OnClickListener() {
	@Override
    public void onClick(View v) {
    	//点击按钮的逻辑
	}
});
  • 通过findViewById()方法获取到在布局文件中定义的元素。findViewById()方法返回的是一个View对象,需要向下转型将它转成Button对象。
  • 通过调用setOnClickListener()方法为按钮注册一个监听器,点击按钮时就会执行监听器中的onClick()方法。监听器有两种构建方式,一种就是上述代码通过View.OnClickListener接口的匿名内部类来构建,还有一种方式,就是实现View.OnClickListener接口,代码如下所示:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		Button button = (Button) findViewById(R.id.button);
		button.setOnClickListener(this);
	}
	@Override
	public void onClick(View v) {
		// 在此处添加逻辑
	}
}

EditText

EditText允许用户在控件里输入和编辑内容,并可以在程序中对这些内容进行处理。除了通用属性,<EditText>标签还有这些属性:

  • android:hint:指定一段提示性的文本。当输入任何内容时,这段文本就会自动消失。
  • android:maxLines:限制EditText最大行数
  • android:maxHeight:限制EditText最大高度
  • android:maxLength:限制EditText最大长度
  • android:maxWidth:限制EditText最大宽度

可通过如下代码来获得EditText的实例,获取EditText中的内容,然后进行相关操作:

EditText editText = (EditText) findViewById(R.id.edit_text);
String text = editText.getText().toString();

ImageView

ImageView是用于在界面上展示图片的一个控件,图片通常都是放在以“drawable”开头的目录下的。<ImageView>标签中除了通用属性,还有android:src属性,这个属性给ImageView指定了一张图片。
可通过以下代码获取ImageView实例,进行相关的操作:

扫描二维码关注公众号,回复: 8568340 查看本文章
ImageView imageView = (ImageView) findViewById(R.id.image_view);
imageView.setImageResource(R.drawable.img);

ImageView的setImageResource()方法将显示的图片改成指定的图片,可用于动态变换图片。

ProgressBar

ProgressBar用于在界面上显示一个进度条,表示程序正在加载一些数据。除了通用属性,<ProgressBar>标签中还有这些属性:

  • style:指定进度条的样式
  • android:max:给进度条设置一个最大值

通过代码可以动态更改进度条的进度,如下所示:

ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
int progress = progressBar.getProgress();
progress = progress + 10;
progressBar.setProgress(progress);

Android控件的可见属性

在所有的Android控件中除了上面的通用属性,还有可见属性:android:visibility,这个属性有三个值:

  • visible:表示控件是可见的,这个值是默认值,不指定android:visibility 时,控件都是可见的。
  • invisible:表示控件不可见,但是它仍然占据着原来的位置和大小,可以理解成控件变成透明状态了。
  • gone:则表示控件不仅不可见,而且不再占用任何屏幕空间。

除了在xml文件中可以设置控件的可见性,还可以通过代码来设置控件可见性:

//以ProgressBar为例,获取控件实例然后设置控件可见性
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
//获取控件可见性
progressBar.getVisibility();
progressBar.setVisibility(View.VISIBLE);
//progressBar.setVisibility(View.INVISIBLE);
//progressBar.setVisibility(View.GONE);

getVisibility();方法用于获取控件的可见性,对应的值为:View.VISIBLE,View.INVISIBLE,View.GONE。

AlertDialog

AlertDialog在当前的界面弹出一个对话框,这个对话框是置顶于所有界面元素之上的,能够屏蔽掉其他控件的交互能力,因此AlertDialog一般都是用于提示一些非常重要的内容或者警告信息。相关代码如下所示:

AlertDialog.Builder dialog = new AlertDialog.Builder (MainActivity.this);
dialog.setTitle("This is Dialog");
dialog.setMessage("Something");
dialog.setCancelable(false);
dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
	@Override
	public void onClick(DialogInterface dialog, int which) {
	}
});
dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
	@Override
	public void onClick(DialogInterface dialog, int which) {
	}
});
dialog.show();

创建AlterDialog实例通过AlertDialog.Builder()来创建,传入当前Activity。AlterDialog常见方法:

  • setTitle():设置对话框标题。
  • setMessage():设置对话框的内容。
  • setCancelable():可否用Back键关闭对话框。
  • setPositiveButton():为对话框设置确定按钮的点击事件。这个方法有两个参数,一个是确认按钮的名称,一个是监听事件。
  • setNegativeButton():设置取消按钮的点击事件。这个方法有两个参数,一个是取消按钮的名称,一个是监听事件。
  • show():显示对话框。

ProgressDialog

ProgressDialog是AlterDialog的子类,功能与AlterDialog相似,不同点在于ProgressDialog会在对话框中显示一个进度条,一般用于表示当前操作比较耗时,让用户耐心地等待。这个类在API level 26 中,ProgressDialog被声明不赞成使用,应使用的替代方法是ProgressBar。
ProgressDialog用法与AlterDialog类似,如下所示:

ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setTitle("This is ProgressDialog");
progressDialog.setMessage("Loading...");
progressDialog.setCancelable(true);
progressDialog.show();

PS:如果在setCancelable()中传入了false,表示ProgressDialog是不能通过Back键取消掉。这时一定要在代码中做好控制,当数据加载完成后必须要调用ProgressDialog的dismiss()方法来关闭对话框,否则ProgressDialog将会一直存在。

基本布局

布局是一种可用于放置很多控件的容器,它可以按照一定的规律调整内部控件的位置,从而编写出精美的界面。布局和控件关系如下图所示
布局和控件

线性布局

LinearLayout称作线性布局,是一种非常常用的布局,这个布局会将它所包含的控件在线性方向上依次排列。<ProgressBar>标签通过android:orientation属性指定排列方向,可选值有两个:

  1. vertical:控件沿垂直方向排列。
  2. horizontal:控件沿水平方向排列,默认的排列方式。

Tips:

  • 如果LinearLayout的排列方向是horizontal,内部的控件就绝对不能将宽度指定为match_parent,因为这样的话,单独一个控件就会将整个水平方向占满,其他的控件就没有可放置的位置了。同样的道理,如果LinearLayout的排列方向是vertical,内部的控件就不能将高度指定为match_parent。
  • 当布局中的控件指定了android:layout_gravity这个属性,LinearLayout的排列方向是horizontal时,只有垂直方向上的对齐方式才会生效,因为此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因而无法指定该方向上的对齐方式。同样的道理,当LinearLayout的排列方向是vertical时,只有水平方向上的对齐方式才会生效。

LinearLayout中控件还有一个重要属性android:layout_weight,这个属性使用比例的方式来指定控件的大小,它在手机屏幕的适配性方面可以起到非常重要的作用。如下所示

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="horizontal"
	android:layout_width="match_parent"
	android:layout_height="match_parent">
	<EditText
		android:id="@+id/input_message"
		android:layout_width="0dp"
		android:layout_height="wrap_content"
		android:layout_weight="1"
		android:hint="Type something"/>
		<!--PS:dp是Android中用于指定控件大小、间距等属性的单位-->
	<Button
		android:id="@+id/send"
		android:layout_width="0dp"
		android:layout_height="wrap_content"
		android:layout_weight="1"
		android:text="Send"/>
</LinearLayout>

使用了android:layout_weight 属性,此时控件的宽度就不应该再由android:layout_width 来决定,这里指定成0dp是一种比较规范的写法。

android:layout_weight如何控制控件大小:系统会先把LinearLayout下所有控件指定的layout_weight值相加,得到一个总值,然后每个控件所占大小的比例就是用该控件的layout_weight值除以刚才算出的总值。

相对布局

RelativeLayout又称作相对布局,也是一种非常常用的布局。RelativeLayout可以通过相对定位的方式让控件出现在布局的任何位置。
在RelativeLayout布局下,控件属性也有非常多:

  • android:layout_alignParentLeft:是否使控件位于父布局左侧(true or false)
  • android:layout_alignParentTop:是否使控件位于父布局顶部(true or false)
  • android:layout_alignParentRight:是否使控件位于父布局右侧(true or false)
  • android:layout_alignParentBottom:是否使控件位于父布局底部(true or false)
  • android:layout_centerInParent:是否使控件位于父布局中间(true or false)
  • android:layout_above:让一个控件位于另一个控件的上方,需要为这个属性指定相对控件id的引用(当一个控件去引用另一个控件的id时,该控件一定要定义在引用控件的后面,不然会出现找不到id的情况)
  • android:layout_below:让一个控件位于另一个控件的下方,需要为这个属性指定相对控件id的引用
  • android:layout_toLeftOf:表示让一个控件位于另一个控件的左侧,需要为这个属性指定相对控件id的引用
  • android:layout_toRightOf:表示让一个控件位于另一个控件的右侧,需要为这个属性指定相对控件id的引用
  • android:layout_alignLeft:让一个控件的左边缘和另一个控件的左边缘对齐
  • android:layout_alignRight:让一个控件的右边缘和另一个控件的右边缘对齐
  • android:layout_alignTop:让一个控件的上边缘和另一个控件的上边缘对齐
  • android:layout_alignBottom:让一个控件的下边缘和另一个控件的下边缘对齐
  • 。。。。。。

RelativeLayout属性较多,可通过属性名称来判断该属性对控件的定位

帧布局

FrameLayout又称作帧布局,它的应用场景少很多。这种布局没有方便的定位方式,所有的控件都会默认摆放在布局的左上角。
可以使用layout_gravity属性来指定控件在布局中的对齐方式,这和LinearLayout中的用法是相似的。

自定义控件

所有控件都是直接或间接继承自View的,所有布局都是直接或间接继承自ViewGroup的。

View是Android中最基本的一种UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,Android的各种控件其实就是在View的基础之上又添加了各自特有的功能。而ViewGroup则是一种特殊的View,它可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器。
常用控件和布局的继承结构

引入布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent" >
	<include layout="@layout/title" />
</LinearLayout>

在一个布局文件中引入另外一个布局文件,使用<include>标签,在layout属性填入要引入的布局id。

若引入的布局是标题栏,还需要将系统自带的标题栏隐藏,隐藏代码如下:

ActionBar actionbar = getSupportActionBar();
if (actionbar != null) {
	actionbar.hide();
}
  • getSupportActionBar()方法来获得ActionBar的实例
  • hide()方法将标题栏隐藏起来

创建自定义控件

自定义控件能解决系统控件不能满足实际需求,重复代码的问题。

以自定义标题栏为例,自定义标题栏控件,代码如下:

public class TitleLayout extends LinearLayout {
	public TitleLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
		LayoutInflater.from(context).inflate(R.layout.title, this);
		//添加其他相关逻辑
	}
}

新建一个类继承View或者View的子类,然后重写构造方法,在构造方法中添加响应的逻辑(还可以重写View中的其他方法)。使用LayoutInflater的from()方法可以构建出一个LayoutInflater 对象,然后调用inflate()方法就可以动态加载一个布局文件。
inflate() 方法接收两个参数:

  1. 第一个参数是要加载的布局文件的id
  2. 第二个参数是给加载好的布局再添加一个父布局

创建好自定义布局之后,需要在需要在其他布局中引用自定义布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <cn.chenjianlink.android.uicustomviews.TitleLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

添加自定义控件和添加普通控件的方式基本是一样的,只不过在添加自定义控件的时候,需要指明控件的完整类名,包名不可以省略。

ListView

程序中有大量的数据需要展示的时候,可以借助ListView来实现。

基本使用

在Activity布局文件中引入ListView控件

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/List_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

ListView是用于展示大量数据的,这些数据一般是数组或者是集合,无法直接导入到ListView中,需要借助适配器来导入数据。一般使用的是ArrayAdapter。它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入。在ArrayAdapter的构造函数中依次传入当前上下文、ListView子项布局的id,以及要适配的数据。

 ArrayAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull T[] objects)  
 ArrayAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull List<T> objects)

构造好适配器对象后,获取ListView实例,调用ListView的**setAdapter()**方法,将构建好的适配器对象传递进去。这样ListView就能显示数据了

定制ListView

官方提供的ListView界面不一定能满足需求,可以自己定制一个。

定制ListView官方提供的适配器可能就不好用了,需要自定义一个适配器。适配器可通过继承ArrayAdapter或是其父类来实现。

@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
	View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
	//添加相关逻辑
}
  • 一般要重写getView方法。这个方法在每个子项被滚动到屏幕内的时候会被调用。
  • 使用LayoutInflater来指定每个子项的布局文件
  • getContext()获取当前Activity
  • 一旦View有了父布局之后,它就不能再添加到ListView中了,所以LayoutInflater的inflate()方法的第三个参数设置为false。
  • convertView这个参数用于将之前加载好的布局进行缓存,可以之后进行重用(可以判断convertView是否为空,若为空,则使用LayoutInflater去加载布局,若不为空,则直接重用)

优化ListView

  • getView(int position, @Nullable View convertView, @NonNull ViewGroup parent),判断convertView是否为空,来进行优化
  • 新建一个ViewHolder类来对控件实例进行缓存,调用View的getTag()方法将ViewHolder存进View中,使用getTag()方法获取ViewHolder对象

点击事件

setOnItemClickListener()方法为ListView注册了一个监听器,当用户点击了ListView中的任何一个子项时,就会回调onItemClick()方法

ListView listView = (ListView) findViewById(R.id.List_view);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
	@Override
	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
		//添加相关逻辑
	}
});

RecyclerView

RecyclerView是一个增强版的ListView,不仅可以轻松实现和ListView同样的效果,还优化了ListView中存在的各种不足之处。目前Android官方更加推荐使用RecyclerView。

添加RecyclerView

RecyclerView属于新增控件,需要在build.gradle文件中添加相应的依赖库,由于AndroidSDK在高版本出现变动,所以添加的依赖有些不同,AndroidSDK版本在28及以上以及Gradle版本为4.6及以上时,dependencies闭包添加如下内容

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
}

在AndroidSdk版本在28以下以及Gradle版本为4.6以下时,dependencies闭包添加如下内容

dependencies {
	compile fileTree(dir: 'libs', include: ['*.jar'])
	compile 'com.android.support:appcompat-v7:24.2.1'
	compile 'com.android.support:recyclerview-v7:24.2.1'
	testCompile 'junit:junit:4.12'
}

基本用法

在布局文件中引入RecyclerView,AndroidSDK版本在28及以上以及Gradle版本为4.6及以上时,使用以下代码引入RecyclerView:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

在AndroidSdk版本在28以下以及Gradle版本为4.6以下时使用以下标签引入RecyclerView:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent">

	<android.support.v7.widget.RecyclerView
		android:id="@+id/recycler_view"
		android:layout_width="match_parent"
		android:layout_height="match_parent" />

</LinearLayout>

引入RecyclerView后,需要给RecyclerView配置一个适配器,新建一个适配器,让这个适配器继承RecyclerView.Adapter类,相关代码如下:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

    static class ViewHolder extends RecyclerView.ViewHolder {

        View MyView;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            //构建ViewHolder的相关逻辑
        }
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.My_item, parent, false);
        final ViewHolder holder = new ViewHolder(view);
        //相关逻辑
        //......
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        //相关逻辑
    }


    @Override
    public int getItemCount() {
        //相关逻辑
    }
}
  • 定义一个内部类ViewHolder,ViewHolder要继承自RecyclerView.ViewHolder。然后ViewHolder的构造函数中要传入一个View参数,这个参数通常就是RecyclerView子项的最外层布局。
  • onCreateViewHolder()、onBindViewHolder()和getItemCount()这三个方法必须重写
  • onCreateViewHolder()方法用于创建ViewHolder实例
  • onBindViewHolder()方法是用于对RecyclerView子项的数据进行赋值的,会在每个子项被滚动到屏幕内的时候执行
  • getItemCount()方法返回RecyclerView一共有多少子项

适配器准备好之后,需要在Activity中进行必要设置:

public class MainActivity extends AppCompatActivity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
		LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
		//下面这句代码是设置布局滚动方向
		linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
		recyclerView.setLayoutManager(linearLayoutManager);
		MyAdapter adapter = new MyAdapter();
		recyclerView.setAdapter(adapter);
		//其他逻辑
	}
}
  • 获取RecyclerView实例,然后创建布局管理器以及适配器,并将这两者绑定到RecyclerView实例中
  • LinearLayoutManager用于指定RecyclerView为线性布局,可实现和ListView一样的效果。可通过LinearLayoutManager的setOrientation()方法来设置布局的排列方向,默认是纵向排列,默认值为LinearLayoutManager.VERTICAL。要设置成横向排列,则传入LinearLayoutManager.HORIZONTAL。
  • ListView的布局排列是由自身去管理的,而RecyclerView则将布局排列交给了LayoutManager,LayoutManager中制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局。除了LinearLayoutManager,RecyclerView还提供了GridLayoutManager(网格布局)和StaggeredGridLayoutManager(瀑布流布局)这两种内置的布局排列方式。
    使用瀑布流布局代码如下:
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
		StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
		recyclerView.setLayoutManager(layoutManager);
		FruitAdapter adapter = new FruitAdapter(fruitList);
		recyclerView.setAdapter(adapter);
	}

StaggeredGridLayoutManager的构造函数接收两个参数,第一个参数用于指定布局的列数;第二个参数用于指定布局的排列方向,传入StaggeredGridLayoutManager.VERTICAL表示会让布局纵向排列(传入StaggeredGridLayoutManager.HORIZONTAL表示让布局横向排列),最后再把创建好的实例设置到RecyclerView当中就可以了

点击事件

RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法,而是需要自行给子项具体的View去注册点击事件,相比于ListView来说,实现起来要复杂一些。这么做的好处是,当点击的是子项里具体的某一个按钮,实现起来比较简单,而ListView实现这个就很困难了。

开发Tips

使用Nine-Patch图片指定图片可被拉伸的部分,防止图片因为拉伸而失真。

发布了20 篇原创文章 · 获赞 1 · 访问量 358

猜你喜欢

转载自blog.csdn.net/weixin_44840161/article/details/102593433