一.常用控件的使用方法
1.TextView
match_parent 由父布局来决定当前控件的大小。
wrap_content 由控件的内容决定当前控件的大小。
**android:gravity: **来指定文字的对齐方式,可选值有top,bottom,left,right,center等,可以用“|”来同时指定多个值,这里我们指定的center,效果等同于center_vertical|center_horizontal.表示文字在垂直和水平方向都居中对齐。
**android:textSize: **指定文字的大小,在Android中字体大小使用sp作为单位。
**android:textColor: **指定文字的颜色。
(#2button)2.Button
android:textAllCaps="false"系统会对Button中的所有英文字母自动进行大小写转换,使用这个配置可以禁用这一默认的属性。
3.EditText
**android:hint: **指定了一段提示性的文本。
**android:maxLines: **指定了EditText的最大行数为两行,这样当输入的内容超过两行时,文本就会向上移动,而EditText则不会在继续拉伸。
String inputText = editText.getText().toString(); Toast.makeText(MainActivity.this, inputText, Toast.LENGTH_SHORT).show();
调用EditText的getText()方法获取到输入的内容,再调用toString()方法转换成字符串。
4.ImageView
项目中有一个空的drawable目录,不过由于这个目录没有指定具体的分辨率,所以一般不使用它来放置图片,这里我们在res目录下新建一个drawable-xhdpi目录。
**android:src: **给imageView指定了一张图片。
imageView.setImageResource(R.drawable.m);
调用ImageView的setImageResource()方法将显示的图片给成m.
5.ProgressBar
ProgressBar用于在界面上显示一个进度条,表示我们的程序正在加载一些数据。
如何才能让进度条在数据加载完成时消失呢?
Anbroid控件的可见属性。所有的Android控件都具有这个属性,可以通过android:visibility进行指定,可选值有三种:visible,invisible和gone。visible表示控件是可见的,这个值是默认值,不指定android:visibility时控件都是可见的。invisible表示控件不可见,但是他仍然占据着原来的位置和大小。可以理解成控件变成透明状态了。gone则表示控件不仅不可见,而且不在占用任何屏幕空间。我们还可以通过代码来设置控件的可见性,使用的是setVisibility()方法,可以传入View.VISIBLE,View.INVISIBLE,View.GONE这三种值。
if (progressBar.getVisibility() == View.GONE) { progressBar.setVisibility(View.VISIBLE); } else { progressBar.setVisibility(View.GONE); }
getVisibility()方法来判断ProgressBar是否可见,如果可见就将ProgressBar隐藏掉,如果不可见就将ProgressBar显示出来。
我们还可以给ProgressBar指定不同的样式,刚刚是圆形进度条,通过style属性可以将它指定成水平进度条。
**android:max: **给进度条设置一个最大值,然后在代码中动态的更改进度条的进度。
int progress = progressBar.getProgress(); progress = progress + 10; progressBar.setProgress(progress);
每点击一次按钮,我们就获取进度条的当前进度。
6.AlertDialog
AlertDialog可以在当前的界面弹出一个对话框,这个对话框是置于所有界面元素之上的,能够屏蔽掉其他控件的交互能力。
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this); dialog.setTitle("This is Dialog"); dialog.setMessage("something inportant"); 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();
首先通过AlertDialog.Builder创建一个AlertDialog的实例,然后可以为这个对话框设置标题,内容,可否取消等属性,接下来调用setPositiveButton()方法为对话框设置确定按钮的点击事件,调用setNegativeButton()方法设置取消按钮的点击事件,最后调用show()方法将对话框显示出来。
7.ProgressDialog
ProgressDialog会在对话框中显示一个进度条,一般用于表示当前操作比较耗时,让用户耐心等待。
ProgressDialog progressDialog = new ProgressDialog(MainActivity.this); progressDialog.setTitle("This is ProgressDialog"); progressDialog.setMessage("Loading..."); progressDialog.setCancelable(true); progressDialog.show();
注意setCancelable()中传入了false,表示ProgressDialog是不能铜鼓back键来取消掉的,这时你就一定要在代码中做好控制,当数据加载完成后必须要调用ProgressDialog的dismiss()方法来关闭对话框,否者ProgressDialog将会一直存在。
二.详解四种基本布局
布局是一种可用于放置很多控件的容器,它可以按照一定的规律调整内部控件的位置,从而编写出精美的界面,当然布局的内部除了放置控件外,也可以放置布局,通过多层布局的嵌套,我们就能够完成一些比较复杂的界面实现。
1.线性布局
LinearLayout:线性布局,是一种非常常用的布局。这个布局会将他所包含的控件在现行方向上依次排列。
android:orientation="vertical"
**android:orientation: **指定排列的方向。如果不指定,默认的排列方向就是horizontal.
如果LinearLayout的排列方向是horizontal,内部的控件就绝对不能将宽度指定为match_parent。因为这样的话,单独一个控件就会将整个水平方向占满,其他的控件就没有可放置的位置了。同样的道理,如果LinraeLayout的排列方向是vertical,内部的控件就不能将高度指定为match_parent.
**android:gravity: **用于指定文字在控件中的对齐方式。
**android:layout_gravity: **指定控件在布局中的对齐方式。注意的是,当LinearLayout的排列方向是horizontal时,只有垂直方向上的对齐方式才会生效,因为此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因而无法指定该方向上的对齐方式。同样的道理,当LinearLayout的排列方向是vertical时,只有水平方向上的对齐方式才会生效。
android:layout_weight=""
android:layout_weight= 允许我们使用比例的方式来指定控件的大小。它在手机屏幕适配性方面可以起到非常重要的作用。
dp:是Android中用于指定控件大小,间距等属性的单位。
2.相对布局
RelativeLayout:相对布局,它可以通过相对定位的方式让控件出现在布局的任何位置。
android:layout_alignParentLeft="true"android:layout_alignParentRight="true"android:layout_alignParentTop="true"android:layout_alignParentBottom="true"android:layout_centerInParent="true"
上面每个控件都是相对于父布局进行定位的。
android:layout_above="@id/button3"android:layout_below="@id/button3"android:layout_toLeftOf="@id/button3"android:layout_toRightOf="@id/button3"
android:layout_above=“ ”:让一个控件位于另一个控件的上方。需要为这个属性指定相对控件id的引用。
android:layout_below="@id/button3" :让一个控件位于另一个控件的下方。
android:layout_toLeftOf="@id/button3" :让一个控件位于另一个控件的左侧。
android:layout_toRightOf="@id/button3" :让一个控件位于另一个控件的右侧。
注意:当一个控件去引用另一个控件的ID时,该控件一定要定义在引用控件的后面,不然会出现找不到ID的情况。
android:layout_alignLeft="@id/button3"android:layout_alignRight="@id/button3"android:layout_alignTop="@id/button3"android:layout_alignBottom="@id/button3"
android:layout_alignLeft="@id/button3" :让一个控件的左边缘和另一个控件的左边缘对齐。
android:layout_alignRight="@id/button3" :让一个控件的右边缘和另一个控件的右边缘对齐。
android:layout_alignTop="@id/button3" :让一个控件的顶部与另一个控件的顶部对齐。
android:layout_alignBottom="@id/button3" :让一个控件的底部与另一个控件的底部对齐。
3.帧布局
FrameLayout:帧布局这种布局没有方便的定位方式,所有的控件都会默认排放在布局的左上角。
4.百分比布局
Android引入了一种全新的布局方式----百分比布局。在这种布局中我们可以不再使用wrap_content,match_parent等方式来指定控件的大小,而是允许直接指定控件在布局中所占的百分比,这样的话就可以轻松实现平分布局甚至是任意比例分割布局的效果了。
由于LinearLayout本身已经支持按比例指定控件的大小,因此百分比布局只为FrameLayout和RelativeLayout进行了功能扩展,提供了PercentFrameLayout和PercentRelativeLayout这两个全新的布局。
Android团队将百分比布局定义在了support库当中,我们只需要在项目的build.gradle中添加百分比布局库的依赖,就能保证百分比布局在Android所有系统版本上的兼容性了。
最外层我们使用了PercentFrameLayout,由于百分比布局并不是内置在系统SDK中的,所以需要把完整的包路径写出来,然后还必须定义一个app的命名空间,这样才能使用百分比布局的自定义属性。
不过PercentFrameLayout还是会继承FrameLayout的特性,即所有的控件默认都是摆放在布局的左上角。
三.系统控件不够用?创建自定义控件
我们所用的所有控件都是直接或间接继承自View的,所用的所有的布局都是直接或间接继承自ViewGroup的,View是安卓中最基本的一种UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,因此,我们使用的各种控件其实就是在View的基础之上有添加了各自特有的功能,而ViewGroup则是一种特殊的View,他可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器。
1.引入布局
android:background : 用于为布局或控件指定一个背景,可以使用颜色或图片来进行填充。
android:layout_margin :指定控件在上下左右方向上的偏移距离。
ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.hide(); }
getSupportActionBar()方法来获得ActionBar的实例,然后在调用ActionBar的hide()方法将标题栏隐藏起来。
2.创建自定义控件
public class TitleLayout extends LinearLayout{ public TitleLayout(Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater.from(context).inflate(R.layout.title,this); }}
我们重写了LinearLayout中带有两个参数的构造函数,在布局中引入TitleLayout控件就会调用这个构造函数,然后在构造函数中需要对标题栏布局进行动态加载,这就要借助LayoutInflater来实现了,通过LayoutInflater的from()函数可以构建出一个LayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件,inflate()方法接收两个参数,第一个参数是要加载的布局文件的id,第二个参数是给加载好的布局在添加一个父布局。
添加自定义控件和添加普通控件的方式基本是一样的,只不过在添加自定义控件的时候,我们需要指明控件的完整类名,包名在这里是不可以省略的。
public class TitleLayout extends LinearLayout{ Button titleBack,titleEdit; public TitleLayout(Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater.from(context).inflate(R.layout.title,this); initView(); initEvent(); } public void initView() { titleBack = (Button) findViewById(R.id.title_back); titleEdit = (Button) findViewById(R.id.title_edit); } public void initEvent() { titleBack.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ((Activity) getContext()).finish(); } }); titleEdit.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getContext(), "You clicked Edit button", Toast.LENGTH_SHORT).show(); } }); }}
上面是自定义控件的点击事件。
四.最常用和最难用的控件——ListView
1.ListView的简单用法
ListView绝对可以称得上是Android中最常用的控件之一,几乎所有的应用程序都会用到它,由于手机屏幕空间有限,能够一次性在屏幕上显示的内容并不多,当我们的程序中有大量的数据需要展示的时候,就可以借助ListView来实现。
private String[] data = { "Apple","Banana","Orange","Watermelon", "Pear","Grape","Pineapple","Strawberry","Cherry","Mango", "Apple","Banana","Orange","Watermelon", "Pear","Grape","Pineapple","Strawberry","Cherry","Mango" };
ArrayAdapter
Android中提供了很多适配器的实现类,其中我认为最好用的就是ArrayAdapter.它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入。ArrayAdapter有多个构造函数的重载,你应该根据实际情况选择最合适的一种.
在ArrayAdapter的构造函数中依次传入当前上下文,ListView子项布局的id,以及要适配的数据。注意:我们使用了android.R.layout.simple_list_item_1作为ListView子项布局的id,这是一个Android内置的布局文件,里面只有一个TextView,可用于简单的显示一段文本。
最后,还需要调用ListView的setAdapter()方法,将构建好的适配器对象传递进去,这样ListView和数据之间的关联就建立完成了。
2.定制ListView的界面
public class FruitAdapter extends ArrayAdapter
FruitAdapter重写了父类的一组构造函数,用于将上下文,ListView子项布局的id和数据都传递进来,又重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。在getView()方法中首先通过getItem()方法得到当前项的Fruit实例,然后使用LayoutInflater来为这个子项加载我们传入的布局。
这里LayoutInflater的inflate()方法接收三个参数,前两个参数我们已经知道是什么了,第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不为这个View添加父布局,因为一旦View有了父布局之后,他就不能再添加到ListView中了。
private void initFruits() { for (int i = 0;i < 2;i++) { Fruit apple = new Fruit("Apple",R.mipmap.ic_launcher); fruitList.add(apple); Fruit banana = new Fruit("Banana",R.mipmap.ic_launcher); fruitList.add(banana); Fruit orange = new Fruit("orange",R.mipmap.ic_launcher); fruitList.add(orange); Fruit watermelon = new Fruit("watermelon",R.mipmap.ic_launcher); fruitList.add(watermelon); Fruit pear = new Fruit("pear",R.mipmap.ic_launcher); fruitList.add(pear); Fruit grape = new Fruit("grape",R.mipmap.ic_launcher); fruitList.add(grape); Fruit pineapple = new Fruit("pineapple",R.mipmap.ic_launcher); fruitList.add(pineapple); Fruit strawberry = new Fruit("strawberry",R.mipmap.ic_launcher); fruitList.add(strawberry); Fruit cherry = new Fruit("cherry",R.mipmap.ic_launcher); fruitList.add(cherry); Fruit mango = new Fruit("mango",R.mipmap.ic_launcher); fruitList.add(mango); } }
用于初始化所有的水果数据,在Fruit类的构造函数中将水果的名字和对应的图片ID传入,然后把创建好的对象添加到水果列表中。
3.提升ListView的运行效率
在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当ListView快速滚动的时候,这就会成为性能的瓶颈。
getView()方法中还有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便以后可以重用。
@Override public View getView(int position, View convertView, ViewGroup parent) { Fruit fruit = getItem(position); View view; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(resourceId, parent,false); }else { view = convertView; } ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image); TextView fruitName = (TextView) view.findViewById(R.id.fruit_name); fruitImage.setImageResource(fruit.getImageId()); fruitName.setText(fruit.getName()); return view; }
我们在getView()方法中进行了判断,如果convertView为null,则使用LayoutInflater去加载布局,如果不为null,则直接对convertView进行重用,这样就大大提高了ListView的运行效率。在快速滚动的时候也可以表现出更好的性能。
虽然现在已经不回去在重复加载布局,但是每次在getView()方法中还是会调用View的findViewById()方法来获取一次控件的实例。我们可以借助一个ViewHolder来对这部分性能进行优化。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
viewHolder = new ViewHolder();
viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
view.setTag(viewHolder); //将ViewHolder存储在View中//
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag(); //重新获取ViewHolder
}
viewHolder.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitName.setText(fruit.getName());
return view;
}
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
我们新增了一个内部类ViewHolder,用于对控件的实例进行缓存,当convertView为null的时候,创建一个ViewHolder对象,并将控件的实例都存放在ViewHolder里,然后调用View的setTag()方法,将ViewHolder对象存储到View中,当convertView不为null的时候,则调用View的getTag()方法,把ViewHolder重新取出,这样所有控件的实例都缓存在了ViewHolder里,就没有必要每次都通过findViewByid()方法来获取控件实例了。
4.ListView的点击事件
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView> parent, View view, int position, long id) { Fruit fruit = fruitList.get(position); Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show(); } });
使用setOnItemClickListener()方法为ListView注册了一个监听器,当用户点击了ListView中的任何一个子项时,就会回调onItemClick()方法。
五,更强大的滚动控件--RecyclerView
ListView
的性能比较差,ListView
的扩展性也不够好,它只能实现数据纵向滚动的效果,如果我们想实现横向滚动的话,ListView
是做不到的。
Android
团队采取了和百分比布局同样的方式,将RecyclerView
定义在了support
库当中。
新建FruitAdapter
类,让这个适配器继承自RecyclerView.Adapter
,并将泛型指定为FruitAdapter.ViewHolder
。其中,ViewHolder
是我们在FruitAdapter
中定义的一个内部类。
public class FruitAdapter extends RecyclerView.Adapter {
private List mFruitList;
public FruitAdapter(List 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();
}
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView fruitImage;
TextView fruitName;
public ViewHolder(View itemView) {
super(itemView);
fruitImage = (ImageView) itemView.findViewById(R.id.fruit_image);
fruitName = (TextView) itemView.findViewById(R.id.fruit_name);
}
}
}
先定义了一个内部类ViewHolder,ViewHolder要继承自RecyclerView.ViewHolder。然后ViewHolder的构造函数中要传入一个View参数,这个参数通常就是RecyclerView子项的最外层布局。那么我们就可以通过findViewById()方法来获取到布局中的ImageView和TextView的实例了。
FruitAdapter中也有一个构造函数,这个方法用于把要展示的数据源传进来,并赋值给一个全局变量mFruitList,我们后续的操作都将在这个数据源的基础上进行。
由于FruitAdapter是继承自RecyclerView.Adapter的,那么就必须重写onCreateViewHolder(),onBindViewHolder()和getItemCount()这三个方法,
onCreateViewHolder()方法是用于创建ViewHolder实例的,我们在这个方法中将fruit_item布局加载进来,然后创建一个ViewHolder()实例,并把加载进来的布局传入到构造函数当中,最后将ViewHolder的实例返回。
onBindViewHolder()方法是用于对RecyclerView子项的数据进行赋值的,会在每个子项被滚动到屏幕内的时候执行,这里我们通过position参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的InageView和TextView当中即可。
getItemCount()方法就非常简单了,它用于告诉RecyclerView一共有多少子项,直接返回数据源的长度就可以了。
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
在onCreate()方法中我们先获取到RecyclerView的实例,然后创建了一个LinearLayoutManager对象,并将它设置到RecyclerView当中,LayoutManager用于指定RecyclerView的布局方式,这里使用的LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。
2.实现横向滚动和瀑布流布局
LinearLayoutManager layoutManager = new LinearLayoutManager(this); layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
调用LinearLayoutManager的setOrientation()方法来设置布局的排列方式,默认是纵向排列的,我们传入LinearLayoutManager.HORIZONTAL表示让布局横行排列,这样RecyclerView就可以横向滚动了。
ListView的布局排列是由自身去管理的,而RecyclerView则将这个工作交给了LayoutManager,LayoutManager中制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局了。
除了LinearLayoutManager之外,RecyclerView还给我们提供了GridLayoutManager和StaggeredGridLayoutManager这两种内置的布局排列方式,GridLayoutManager可以用于实现网格布局,StaggeredGridLayoutManager可以用于实现瀑布流布局。
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL); recyclerView.setLayoutManager(layoutManager);
StaggeredGridLayoutManager的构造函数接收两个参数,第一个参数用于指定布局的列数,传入3表示会把布局分为3列,第二个参数,用于指定布局的排列方向,传入StaggeredGridLayoutManager.VERTICAL表示会让布局纵向排列。
3.RecyclerView的点击事件
ListView在点击事件上 的处理并不人性化,setOnitemClickListener()方法注册的是子项的点击事件,但如果我想点击的是子项里具体的某一个按钮呢?虽然ListView也是能做到的,但是实现起来就相对比较麻烦了,为此,RecyclerView干脆直接摒弃了子项点击事件的监听器,所有的点击事件都有具体的View去注册,就在没有这个困扰了。
@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 v) { int position = holder.getAdapterPosition(); Fruit fruit = mFruitList.get(position); Toast.makeText(v.getContext(), "You click view" +fruit.getName(), Toast.LENGTH_SHORT).show(); } }); holder.fruitImage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int position = holder.getAdapterPosition(); Fruit fruit = mFruitList.get(position); Toast.makeText(v.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(); } class ViewHolder extends RecyclerView.ViewHolder { ImageView fruitImage; TextView fruitName; View fruitView; public ViewHolder(View itemView) { super(itemView); fruitView = itemView; fruitImage = (ImageView) itemView.findViewById(R.id.fruit_image); fruitName = (TextView) itemView.findViewById(R.id.fruit_name); } }
在ViewHolder中添加了fruitView变量来保存子项最外层布局的实例,然后在onCreateViewHolder()方法中注册点击事件就可以了。这里分别为最外层布局和ImageView都注册了点击事件,RecyclerView的强大之处也在这里,它可以轻松实现子项中任意控件或布局的点击事件。我们在两个点击事件中先获取了用户点击的position,然后通过position拿到相应的Fruit实例。