UI开发的点点滴滴
如何编写程序界面
Android中有多种编写程序界面的方式可供选择。Android Studio和 Eclipse总都提供了相应的可视化编辑器,允许使用拖放控件的方式来编写布局,并能在试图上直接修改控件的属性
缺点:可视化编辑工具不利于真正了解界面背后的实现原理,通过这种方式制作出的界面通常不具有很好的屏幕适配性,而且当需要编写较复杂的界面时,可视化编辑工具将很难胜任
常见控件的使用方法
TextView
match_parent:让当前控件的大小和福布局的大小一样,也就是由父布局界定当前控件的大小
wrap_content:让当前控件的大小能够刚好包含住里面的内容,也就是由控件内容决定当前控件的大小
android:gravity:可选值有top、bottom、left、right、center等,可以用”|”来同时制定多个值,制定center效果等同于center_vertical|center_horizontal,表示文字在垂直和水平方向都居中
android中字体大小可以使用sp作为单位
Button
android:textAllCaps=”false” 禁用大小写转换
EditText
android:hint属性指定了一段提示性的文本
android:maxLines=”2”指定了EditText的最大行数为两行,这样当输入的内容超过两行时,文本就会向上滚动,而EditText则不会再继续拉伸
ProgressBar
style="?anddroid:attr/progressBarStyleHorizontal"
style="@anddroid:style/Widget.progressBar.Horizontal"
使用@表示侍弄固定的style,而不会跟随Theme改变,这style可以在对应的style.xml中找到。而?表示从theme中查找引用的资源名
visable表示控件是可见的,这个值是默认的
不指定android:visiblity时,控件都是可见的。invisable表示控件不可见,但是它仍然占据着原来的位置和大小,可以理解成控件变成透明状态了。gone则表示控件不仅不可见,而且不再战虎任何屏幕空间
我们还可以通过代码来设置控件的可见性,使用的是setVisibility()方法,可以传入View.VISIBLE、View.INVISIBLE和View.GONE 这3种值
通过android:max属性给进度条设置一个最大值,然后在代码中动态地更改进度条的进度
AlertDialog
AlertDialog可以在当前的界面弹出一个对话框,这个对话框是置顶于所有界面元素之上的,能够屏蔽掉其他控件的交互能力,因此AlertDialog一般都是用于提示一些非常重要的内容或者警告信息。比如为了防止用户误删重要内容,在删除前弹出一个确认对话框
@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 view) {
switch (view.getId()) {
case R.id.button:
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
dialog.setTitle("This is Dialog");
dialog.setMessage("Something important.");
dialog.setCancelable(false);
//确认
dialog.setPositiveButton("ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
//取消
dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
dialog.show();
break;
default:
break;
}
}
ProgressDialog
ProgressDialog和AlertDialog优点类似,都可以在界面上弹出一个对话框,都能够屏蔽掉其他空间的交互能力。不同的是,ProgressDialog会在对话框中显示一个进度条,一般用于表示当前操作比较耗时,让用户耐心等待
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.button:
ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setTitle("This is ProgressDialog");
progressDialog.setMessage("loading...");
progressDialog.setCancelable(true);
progressDialog.show();
break;
default:
break;
}
}
先构建出一个ProgressDialog对象,然后同样可以设置标题、内容、可否取消等属性,最后也是通过show()方法将ProgressDialog显示出来
注意:如果在setCancelable()中传入了false,表示ProgressDialog是不能通过back键取消的,当数据加载完成后必须要调用ProgressDialog的dismiss()方法来关闭对话框,否则ProgressDialog将会一直存在
详解四种基本布局
线性布局
将其所包含的控件在现行方向上依次排列
LinearLayout的排列方向是horizontal,内部的控件就绝对不能将宽度指定为match_parent,因为这样的话,单独的一个控件会将整个水平方向占满,其他的空间就没有可放置的位置了
android:layout_gravity用于指定控件在布局中国的对齐方式
LinearLayout的排列方向是horizontal时,只有垂直方向上的对齐方式才会生效,因为此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变因而无法指定该方向上的对其方式;同样当LinearLayout的排列方向是vertical时只有水平方向上的对其方式才会生效
android:layout_weight属性允许我们使用比例的方式来指定控件的大小,它在手机屏幕的适配性方面可以起到非常重要的作用
dp是android中用于指定控件大小、属性的单位
系统会把LinearLayout下所有控件指定的layout_weight值相加,得到一个总值,然后每个控件所占大小的比例就是该控件的layout_weight值除以刚才算出的总和
相对布局
RelativeLayout又称作相对布局,也是一种非常常用的布局
android:layout_above属性可以让一个控件位于另一个控件的上方
layout_alignLeft表示让控件的左边缘和另一个控件的左边缘对齐
帧布局
FrameLayout又称作帧布局,这种布局没有方便的定位方式,所有的控件都会默认八方在布局的左上角
百分比布局
提供了PercentFrameLayout和PercentRelativelayout这两个全新的布局
android团队将百分比布局定义在support库当中,我们只需要在项目的build.gradle中添加百分比布局库的依赖,就能保证百分比布局在android所有系统版本上的兼容性了
PercentFrameLayout还是会继承FrameLayout的特性,即所有的控件默认都是摆放在布局的左上角
创建自定义控件
所有控件都是直接或间接继承自view的,所欧阳的所有布局都是直接或间接继承自ViewGroup的。View是Android中最基本的一种UI 组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,因此,我们使用的各种控件其实就是在View的基础之上又添加了各自特有的功能。而ViewGroup则是一种特殊的View,它可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器
引入布局
创建title.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="wrap_content"
android:background="@drawable/title_bg">
<Button
android:id="@+id/title_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:background="@drawable/back_bg"
android:text="back"
android:textColor="#fff"
/>
<TextView
android:id="@+id/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:text="Title Text"
android:textColor="#fff"
android:textSize="24sp"
/>
<Button
android:id="@+id/title_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:background="@drawable/edit_bg"
android:text="edit"
android:textColor="#fff"
/>
</LinearLayout>
在activity_main.xml中
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.demo.uicustomviews.MainActivity">
<include layout="@layout/title"/>
</LinearLayout>
在MainActivity中将系统自带的标题栏隐藏掉
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//隐藏系统自带的标题栏
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
}
}
}
创建自定义控件
新建TitleLayout继承自LinearLayout,让它成为我们自定义的标题栏控件
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title , this);
}
}
通过LayoutInflater的from()方法可以构建一个LayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件,inflate()方法接收两个参数,第一个参数是要加载的布局文件的id,这里我们传入R.layout.title,第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为TitleLayout,于是直接传入this
在布局文件中添加这个自定义控件,修改activity_main.xml中的代码,如下:
<com.demo.uicustomviews.TitleLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
在标题栏中的按钮注册点击事件,修改TitleLayout中的代码:
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title , this);
Button titleBack = (Button) findViewById(R.id.title_back);
Button titleEdit = (Button) findViewById(R.id.title_edit);
titleBack.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
((Activity) getContext()).finish();
}
});
titleEdit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getContext() , "You clicked Edit button" , Toast.LENGTH_SHORT).show();
}
});
}
}
RecyclerView
可以轻松实现了和listView同样的效果,还优化了ListView中存在的各种不足之处
为RecyclerView准备一个适配器,新建一个FruitAdapter类,让这个适配器继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder。
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView fruitImage;
TextView fruitName;
public ViewHolder (View view) {
super(view);
fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
fruitName = (TextView) view.findViewById(R.id.fruit_name);
}
}
public FruitAdapter (List<Fruit> fruitList) {
mFruitList = fruitList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item , parent , false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount() {
return mFruitList.size();
}
}
首先定义了一个内部类ViewHolder,ViewHolder要继承自RecyclerView.ViewHolder。然后ViewHolder的构造函数中要传入一个view参数,这个参数通常就是RecyclerView子项的最外层布局,那么我们就可以通过findViewById()来获取布局中的 ImageView和TextView的实例了
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
/**
* 初始化水果数据
*/
private void initFruits() {
for (int i = 0 ; i < 2 ; i ++) {
Fruit apple = new Fruit("Apple" , R.drawable.apple_pic);
fruitList.add(apple);
Fruit banana = new Fruit("Banana" , R.drawable.banana_pic);
fruitList.add(banana);
Fruit orange = new Fruit("Orange" , R.drawable.orange_pic);
fruitList.add(orange);
Fruit watermelon = new Fruit("Watermelon" , R.drawable.watermelon_pic);
fruitList.add(watermelon);
Fruit pear = new Fruit("Pear" , R.drawable.pear_pic);
fruitList.add(pear);
Fruit grape = new Fruit("Grape" , R.drawable.grape_pic);
fruitList.add(grape);
Fruit pineapple = new Fruit("Pineapple" , R.drawable.pineapple_pic);
fruitList.add(pineapple);
Fruit strawberry = new Fruit("Strawberry" , R.drawable.strawberry_pic);
fruitList.add(strawberry);
Fruit cherry = new Fruit("Cherry" , R.drawable.cherry_pic);
fruitList.add(cherry);
Fruit mango = new Fruit("Mango" , R.drawable.mango_pic);
fruitList.add(mango);
}
}
}
首先初始化所有的水果数据,接着在onCreate()方法中我们先获取到RecyclerView的实例,然后创建一个LinearLayoutManager对象,并将它设置到RecyclerView当中。layoutManager用于指定recyclerView的布局方式,这里使用LinearLayoutManager时线性布局的意思,可以实现和listView类似的效果。是接下来创建FruitAdapter的实例,并将水果数据传入到FruitAdapter的构造函数中,最后调用RecyclerView的setAdapter()方法来完成适配器设置,这样RecyclerView和数据支架的关联就建立完成了
实现横向滚动和瀑布流布局
修改fruit_item.xml布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp" />
</LinearLayout>
在MainActivity中加入一行代码
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
用LinearLayoutManager的setOrientation()方法来设置布局的排列方向,默认是纵向排列的,传入LinearLayoutManager.HORIZONTAL表示让布局横向排列
ListView和RecyclerView的区别:
ListView的布局排列是由自身去管理的,而RecyclerView则将这个工作交给 LayoutManager ,LayoutManager中指定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局
除此之外,RecyclerView还给我们提供了GridLayoutManager和StaggredGridLayoutManager这两种内置的布局方式。GridLayoutManager可以用于实现网格布局,StaggredGridLayoutManager可以用于实现瀑布流布局
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();
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);
}
/**
* 初始化水果数据
*/
private void initFruits() {
for (int i = 0; i < 2; i++) {
Fruit apple = new Fruit(getRandomLengthName("Apple"), R.drawable.apple_pic);
fruitList.add(apple);
Fruit banana = new Fruit(getRandomLengthName("Banana"), R.drawable.banana_pic);
fruitList.add(banana);
Fruit orange = new Fruit(getRandomLengthName("Orange"), R.drawable.orange_pic);
fruitList.add(orange);
Fruit watermelon = new Fruit(getRandomLengthName("Watermelon"), R.drawable.watermelon_pic);
fruitList.add(watermelon);
Fruit pear = new Fruit(getRandomLengthName("Pear"), R.drawable.pear_pic);
fruitList.add(pear);
Fruit grape = new Fruit(getRandomLengthName("Grape"), R.drawable.grape_pic);
fruitList.add(grape);
Fruit pineapple = new Fruit(getRandomLengthName("Pineapple"), R.drawable.pineapple_pic);
fruitList.add(pineapple);
Fruit strawberry = new Fruit(getRandomLengthName("Strawberry"), R.drawable.strawberry_pic);
fruitList.add(strawberry);
Fruit cherry = new Fruit(getRandomLengthName("Cherry"), R.drawable.cherry_pic);
fruitList.add(cherry);
Fruit mango = new Fruit(getRandomLengthName("Mango"), R.drawable.mango_pic);
fruitList.add(mango);
}
}
private String getRandomLengthName(String name) {
Random random = new Random();
int length = random.nextInt(20) + 1;
StringBuilder builder = new StringBuilder();
for (int i = 0 ; i < length ; i ++) {
builder.append(name);
}
return builder.toString();
}
}
RecyclerView的点击事件
修改FruitAdapter中的代码
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder {
View fruitView;
ImageView fruitImage;
TextView fruitName;
public ViewHolder (View view) {
super(view);
fruitView = view;
fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
fruitName = (TextView) view.findViewById(R.id.fruit_name);
}
}
public FruitAdapter (List<Fruit> fruitList) {
mFruitList = fruitList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item , parent , false);
final ViewHolder holder = new ViewHolder(view);
//实现子项中布局的点击事件
holder.fruitView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = holder.getAdapterPosition();
Fruit fruit = mFruitList.get(position);
Toast.makeText(view.getContext() , "you clicked view" + fruit.getName() , Toast.LENGTH_SHORT).show();
}
});
//实现子项中ImageView的点击事件
holder.fruitImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = holder.getAdapterPosition();
Fruit fruit = mFruitList.get(position);
Toast.makeText(view.getContext() , "you clicked image" + fruit.getName() , Toast.LENGTH_SHORT).show();
}
});
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount() {
return mFruitList.size();
}
}