UI初步介绍
UI界面很重要。
1、如何编写界面
Android Studio提供可视化编辑器,但不推荐使用,因为屏幕适配性差。
推荐XML编码方式编写界面。
2、常用控件
Android提供大量内置控件。
1、TextView
作用:在界面上显示一段文本信息
代码:
<TextView
android:id=”@+id=text_view”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”This is TextView” />
介绍主要属性:
android:id给当前控件定义一个唯一标识符,可使用findViewById()来获得该控件实例
android:layout_width和android:layout_height是所有控件必备属性,不然会报错。可选值有:match_parent、wrap_content、fill_parent。match_parent与fill_parent相同,推荐match_parent。
不推荐给他们赋固定值,屏幕适配性差。
android:text制定显示的内容
TextView默认文字内容左上角对齐
android:gravity=”center”表示<TextView>里的子项(比如文字内容)对齐方式是居中
android:layout_gravity=”center”表示<TextView>在父布局中的对齐方式是居中
以上两种属性的可选值基本相同
所有gravity属性都可以用”|”来指定多值,”center_vertical|center_horizontal
android:textSize=”24sp”指定文字大小,单位是”sp”
android:textColor=”#00ff00”指定文字颜色
2、Button
<Button
android:id=”@+id=button”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”Button”
android:textAllCaps=”false” />
介绍主要属性:
Android:textAllCaps=”false”,系统会对Button中的所有英文字母自动进行大小写转换,该属性禁用这一默认特性
为Button点击事件注册一个监听器:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
Button button = (Button) findViewById(R.id.button);
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//在此处添加逻辑
}
});
}
}
onClick()方法是实例化内部接口的同时,实例化匿名对象,基本上Android中监听方法都这么写
另一种Button点击事件注册监听器方法:
public class MainActivity extends AppCompatActivity implements View.OnclickListener {
//View.OnClickListener为静态内部类,所以继承它时构造方法不需要外部对象的引用,直接当普通类一样
@Override
protected void onCreate(Bundle savedInstanceState) {
...
Button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch(v.getId()) {
case R.id.button:
//在此处添加逻辑
break;
default:
break;
}
}
}
采用这种写法时,因为MainActivity中所有点击事件的具体逻辑都是通过调用onClick方法来完成的,所以要在onClick方法中对具体的触发View进行判断。而采用匿名实例化内部类的方式,则不需要这么做。
3、EditText
介绍主要属性:
android:hint=”Type something here制定提示性文本,在输入任何内容后自动消失
android:maxLines指定最大行数,超过文本自动向上滚动
获取EditText内容的方法:
获得该EditText的实例然后使用getText()方法,最后toString()转化为字符串
4、ImageView
Android项目的图片资源一般都存放在”drawable”开头的目录下,具体根据分辨率有几个子目录比如:”drawable-xhdpi”
介绍主要属性:
Android:src=“@drawable/img_1”指定一张图片
ImageView类的一些方法:
imageView.setImageResource(R.drawable.img_2)给ImageView控件指定图片资源
5、ProgerssBar
进度条控件
所有的Android控件都具有android:visibility属性,有三个可选值:visible、invisible、gone。 visible表示控件可见、invisible表示控件不可见,但仍占据原来位置和大小,即控件透明化、gone表示控件不可见而且不占用任何屏幕控件。
还可通过代码设置控件的可见性,使用setVisiblity()方法,传入View.VISIVLE、View.INVISIBLE、GONE三种值
介绍主要属性:
style=”?android:attr/progressBarStyleHorizontal”指定不同样式进度条
android:max=”100”给进度条设置一个最大值
设置及改变进度条进度方法:
Int progress = progressBar.getProgress();
Progress = progress + 10;
progressBar.setProgress(progress);
6、AlertDialog
AlertDialog可以在当前界面弹出一个对话,该对话框置顶于多有界面元素智商,能够屏蔽掉其他空间的交互能力,一般用于提示重要内容及警告信息。
使用方法:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
...
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
AlertDialon.Builder dialog = new AlerDialog.Builder(MainActiviyy.this);
dialong.setTitle(“This is import”);
dialog.setCancelable(“false”);//设置为false,按返回键不能退出,默认为true
dialog.setPositiveButton(“OK”, new DialongInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialong,int which) {
//具体逻辑或者什么都不写,点击完成后AlertDialog消失
}
});
dialog.setNegativeButton(“Cancel”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialong,int which) {
//具体逻辑或者什么都不写,点击完成后AlertDialog消失
}
});
dialog.show();
break;
default:
break;
}
}
}
AlertDialog.Builder创建AlertDialog实例
3、ProgressDialog
与ProgressDialog很像但是多了一个进度条,一般用于表示当前操作比较耗时
使用方法:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
...
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);//获得实例的方式与AlertDialog不一样
progressDialog.setTitle(“This is ProgressDialog”);
progressDialog.setMessage(“Loading...”);
progressDialog.setCancelable(true);//如果传入false,一定要在代码中做好控制,数据加载完之后一定要调用PrgoressDialog的dissmiss()方法关闭对话框。不然数据加载完ProgressDialog会一直存在。
progressDialog.show();
break;
default:
break;
}
}
}
3、四种基本布局
布局是一种放置很多控件的容器,它可以按照一定的规律调整内部控件的位置
布局可以嵌套
Android有四种基本布局
1、线性布局
所包含控件在线性方向上依次排列
android:orientation=”vertical”
anroid:orientation=”horizontal”
默认为horizontal
当orientation=”horizontal”时子项的layout_width就不能为match_parent,而且layout_gravity只有垂直方向上的对齐方式才有效,因为水平方向上长度不固定,每添加一个控件,水平方向上的长度都会改变,无法指定该方向上的对齐方式。
当orientation=”vertical”时子项的layout_heigth就不能为match_parent,而且layout_gravity只有水平方向上的对齐方式才有效
介绍主要属性:
android:layout_weight这个属性允许我们使用比例的方式来指定控件的大小,屏幕适配性非常好。代码如下:
<LinearLayout ...
...>
<EditText
...
android:layout_width=”0dp”
android:layout_height=”wrap_content”
android:layout_weigth=”1”
.../>
<Button
...
Android:layout_width=”0dp”
Android:layout_heigth=”wrap_content”
Android:layout_weight=”1”
.../>
</LinearLayout>
当指定layout_weigth属性后layout_width属性无效,设置为0dp
dp是Android中用于指定控件大小、间距等属性的单位
系统会把LinearLayout下所有的控件指定的layout_weigth值相加,得到一个总值,然后每个控件所占大小的比例就是用该控件的layout_weight值处以刚才算出的总值。Ps:这个比例是子项相对于母项
当同级子项中一个为wrap_content或者其他未指定layout_weight时,剩下的子项分配剩下的空间。比如:EditText的layout_weigth=”1”,Button的layout_width=”wrap_content”时,Button占据固定空间后剩下的空间全部被EditText占据。这样做,屏幕适配性好。
2、相对布局
通过相对定位的方式让控件出现在布局的任意位置
属性较多
相对于父布局定位:
<RelativeLayout
...
android:layout_width=”match_parent”
android:layout_heigth=”match_parent”>
<Button
...
android:layout_alignParentLeft=”true”
android:layout_alignParenTop=”true”
.../>
<Button
...
android:layout_alignParentRigth=”true”
android:layout_alignParentTop=”true”
.../>
<Button
...
android:layout_centerInParent=”true”
.../>
<Button
...
android:layout_alignParentLeft=”true”
android:layout_alignParentBottom=”true”
.../>
<Button
...
android:layout_alignParentRight=”true”
android:laoyout_alignParentBottom=”true”
.../>
</RelativeLayout>
相对控件定位:
<RelativeLayout
...
android:layout_width=”match_parent”
android:layout_height=”match_parent”>
<Button
...
android:layout_centerInParent=”true”
.../>
<Button
...
android:layout_above=”@id/button3”//此时button3的位置一定要先确定!!!!
android:layout_toLeftOf=”@id/button3”
.../>
<Button
...
android:layout_above=”@id/button3”
android:layout_toRightOf=”@id/button3”
.../>
<Button
...
android:layout_below=”@id/button3”
android:layout_toLeftOf=”id/button3”
.../>
<Button
android:layout_below=”@id/button3”
android:layout_toRightOf=”@id/button3”
.../>
</RelativeLayout>
当一个控件去引用另一个控件的id时,该控件一定要定义在引用控件后面!不然会出现找不到id的情况!Ps:如果不这样的话,前一个控件位置都不知道那么后一个位置怎么相对它定位呢?
介绍属性:
android:layout_alignLeft表示让一个控件的左边缘和另一个控件的左边缘对齐
android:layout_alignRight
android:layout_alignTop
android:layout_alignBottom
3、帧布局
该布局没有方便的定位方式,所有控件都默认摆放在布局的左上角
后添加的控件会压在先添加的控件之上
可以使用layout_gravity来指定控件在布局中的对齐方式(位置)
该布局定位方式欠缺,应用场景较少
Android:src=”@mipmap/ic_launcher”引用系统自带图片(安卓图标)
4、百分比布局
只有LinearLayout支持layout_weight,Relavtive和FrameLayout都不支持
Android引入一种全新布局方式来解决此问题-百分比布局
在百分比布局中不再使用wrap_content、match_parent来指定控件大小,而是允许直接指定控件在布局中所占的百分比
百分比布局只为FrameLayout和RelativeLayout提供功能扩展,提供了PercentFrameLayout和
PercentRelativeLayout这两个全新的布局
百分比布局不属于系统自带布局,属于新增布局。Android团队将PercentLayout定义在support库中,我们需要在项目(app目录下)的build.gradle中添加PercentLayout库的依赖
添加依赖:
dependencies {
complie fileTree(dir: ‘libs’, include: [‘*.jar’])
complie ‘com.android.support:appcompat-v7:26.0.0-a1pha1’
complie ‘com.android.support:percent:26.0.0-a1pha1’
testCompile ‘junit:junit:4.12’
}//注意percent的版本不能低于appcompat的版本!!!
修改了build.gradle文件之后,系统会弹出提示,点击Sync Now同步gradle,把新添加的依赖添加到项目中
布局代码:
<android.support.percent.PercentFrameLayout
xmlns:app=”http://schemas.android.com/apk/res-auto”
...
android:layout_width=”match_parent”
android:layout_height=”match_parent”>
<Button
...
android:layout_gravity=”left|top”
app:layout_widthPercent=”50%”
app:layout_heigthPercent=”50%”/>
<Button
...
android:layout_gravity=”right|top”
app:layout_widthPercent=”50%”
app:layout_heightPercent=50%”/>
<Button
...
android:layout_gravity=”left|bottom”
app:layout_widthPercent=”50%”
anpp:layout_heightPercent=50%”/>
<Button
...
android:layout_gravity=”right|bottom”
app:layout_widthPercent=”50%”
app:layout_heightPercent=50%”/>
</android.support.percent.PercentFrameLayout>
由于百分比布局不是内置在系统SDK当中的,所以需要把完整的包路径写出来。然后还必须定义一个app命名空间,这样才能使用百分比布局的自定义属性
老版本Android Studio内置布局检查机制,认为每一个控件都要指定layout_width和layout_height才合法。所以上面老版本IDE会报错但是能运行。
4、创建自定义控件
控件和布局的继承结构:
所有控件都直接或间接继承自View
所有布局都直接或间接继承自ViewGroup
View是Android中最基本的一种UI组件,它可以在屏幕上绘制一块矩形区域,并能相应这块区域的各种事件
ViewGroup是一种特殊的View,它可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器
1、引入布局
如果每个活动中都编写一遍同样的布局代码,导致代码大量重复,可以使用引入布局的方式来解决这个问题
新建title.xml标题布局,代码略
android:backgroud用于为布局或控件指定一个背景,可以使用颜色或者图片填充
android:layout_margin指定控件在上下左右方向上偏移的距离
android:layout_marginLeft、android:layout_marginTop指定特定方向上偏移距离
布局引入方法:
<LinearLayout
....>
<include layout=”@layout/title”/>
...
</LinearLayout>
只需要一行<include .../>就可以引入布局
隐藏系统自带标题栏方法:
public class MainActivity extends AppCompatActivty {
@Override
protected void onCreate(Bundle savedInsatnceState) {
...
ActionBar actionBar = getSupportActionBar();
if(actionBar != null) {
actionBar.hide();
}
}
}
2、创建自定义控件
引入布局的技巧解决了重复编写布局代码的问题,但是如果布局中有一些控件要求能够响应事件,我们还是需要在每个活动中编写相同的逻辑代码,此时最好使用自定义控件来解决代码重复问题
自定义控件方法:
新建TitleLayout继承自LinearLayout,让她成为我们自定义的标题栏控件,代码如下:
public class TitleLayout extends LinearLayout {//继承Layout是因为Layout的父类使ViewGroup,ViewGroup是各种控件的容器。
public TitleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
}
}
LayoutInflater.from(context)构建一个LayoutInflater对象,这里的context是指Activity
Inflater(R.layout.title, this);动态加载布局,返回布局View实例。
Inflater两个参数:第一个是资源文件id,第二个是父布局/父控件,这里加入this即TitleLayout父控件,用来容纳之前写好的子布局(标题栏)。
这个地方一定要好好理解,此处,新的控件是父布局,而我们之前写好的title.xml是子布局!所以,this在parent位置。
Ps:关于LayoutInflater单独讲解。
自定义控件创建完成
自定义控件的使用:
<LinearLayout xlmns:anroid=”http:schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>
<com.example.seele.uicustomviews.TitleLayout//添加自定义控件要使用完整类名
android:layout_width=”match_parent”
android:layout_height=”wrap_content” />
</LinearLayout>
在TitleLayout类中为标题栏中的按钮注册点击事件,以后每当我们在一个布局中引入TitleLayout时,按钮的点击事件就已经自动实现了,省略大量重复代码。
5、ListView
1、ListView简单用法(最简单的用法)
Activity_main.xml:
引入ListView控件
MainActivity.java:
Public class Activity extends AppCompatActivity {
private String[] data = {“apple”,...};
@Override
protected void onCreate(Bundle savedInstanceState) {
...
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
MainActivity.this, android.R.layout.simple_list_item_1, data);
ListView listView = (ListView) findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
}
通过适配器传递数据给ListView。
Android内置多种适配器,其中ArrayAdapter较方便,支持泛型指定适配数据类型。且有多个构造函数重载。
这里ArrayAdapter构造函数参数一次传入context,ListView子项布局id,适配数据。
Android.R.layout.simple_list_item_1是内置布局文件。里面只有一个TextView。
2、定制ListView界面
定制实体类Fruit
自定义ListView子项布局fruit_item.xml
自定义适配器、覆写getView方法。
只需要修改fruit_item.xml中内容,就可以定制各种复杂界面。
自定义适配器:
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) {
super(context, textViewResouceId, objects);
resourceId = textViewResourceId;
}
@Override
pubic View getView(int position, View convertView, ViewGroup paretn) {
Fruit fruit = getItem(postiont);//getItem方法获得当前项的Fruit实例
① View view = LayoutInflater.from(getContext).inflate(resourceId, parent, false);
ImageView fruitImage = (ImageView) findViewById(R.id.fruit_image);
TextView fruitName = (TextView) findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
}
MainActivit.java中的使用方法与普通ListView相同
3、提升ListView运行效率!!!
未经优化的ListView效率很低,主要原因在于布局反复加载,以及反复使用findViewById获得控件实例!
优化布局反复加载:
getView中有一个convertView参数,用这个参数对之前加载好的布局进行缓存。
将上面①行代码更换为如下代码:
View view;
if(convertView == null) {
view = LayouInflater.from(getContext()).inflate(resourceId, parent, false);
} else {
view = convertView;
}//当子布局被第一次加载之后convertView就会获得这个View实例,下次就不必再次加载。这个view实例最终会被一个方法add(view)加入进去。
优化控件实例反复获取:
虽然不必重复加载布局了,但仍然要反复去获取布局中的控件实例。
借助ViewHolder来对这部分优化。
代码如下:
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) {
super(context, textViewResouceId, objects);
resourceId = textViewResourceId;
}
@Override
pubic View getView(int position, View convertView, ViewGroup paretn) {
Fruit fruit = getItem(postiont);//getItem方法获得当前项的Fruit实例
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);
} else {
view = convertView;
viewHodler = (ViewHolder) view.getTag();
}
viewHodler.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitName.setText(fruit.getName());
return view;
}
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
通过内部类ViewHolder对控件实例进行缓存。
经过以上优化后,效力已经非常不错了。
4、ListView点击事件
ListView的点击事件只能到达ListView的子项,再深入时较麻烦。
只为每一个子项注册监听器。当点击ListView每一个子项时,就会回调点击方法。
代码:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Fruit fruit = fruitList.get(position);
Toast...show();
}
});//这里只关心一个参数:position。
6、RecyclerView
1、RecyclerView基本用法
RecyclerView属于新增控件,定义在support库中,需在build.gradle中添加相应依赖库:
dependencies {
...
compile ‘com.android.support:recyclerview-v7:26.0.0-a1pha1’//版本号不能低于//appcompat版本号,后缀里面第一个是字母l第二个是数字1l。
...
}
添加完之后要点击Sync Now来进行同步。
在activity_main.xml中添加RecyclerView控件。因为是新增控件所以需要完整类名。
使用与ListView相同的Fruit类以及fruit_item.xml布局
为RecyclerView准备适配器://必须覆写下面三个方法!!!
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(posotion);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount() {
return mFruitList.size();
}
}
适配器准备好之后,在MainActivity中使用RecyclerView:
Public class MainActivity extends AppCompatActivity {
Private List<Fruit> fruitList = new ArrayList<>();
@Override
Protected void onCreate(Bundle savedInstanceState) {
Super.onCreate(savedInstanceStae);
setContentView(R.layout.activity_main);
iniFruits();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayouManager(this);
recyclerView.setLayouManager(layoutManager);//指定RecyclerView的布局方式
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
Private void iniFruits() {
//给fruitList添加一些数据
}
}
2、实现横向滚动和瀑布流布局
横向滚动:
横向滚动只需要修改LayoutManager:
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
这样LayoutManager的默认纵向排列被改为横行排列
瀑布流:
LayouManager:StaggerGridLayoutManager layoutManager = new StaggerGridManager(3, StaggeredGridLayoutManager.VERTICAL);就可实现瀑布流。第一个参数是列数,第二个参数是排列方向。
3、RecyclerView的点击事件
RecyclerView没有提供类似setOnItemClickListener()这样的注册监听器方法,而是需要给子项具体的View去注册监听事件。
注册代码如下:
在ViewHolder中实例化fruitView
在onCreateViewHolder方法中:
final ViewHolder holder = new ViewHolder(View);
holder.fruitView.setOnClickListener(new View.OnClickListener() {
....
});//给fruitView注册监听事件
holder.fruitImage.setOnClickListener(new View.OnclickListener() {
...
});//给fruitImage注册监听事件
上面分别为最外层布局和ImageView都注册了点击事件,RecyclerView的强大之处也是在这里。当我们点击TextView控件时,最外层布局点击事件就会被触发。
7、编写聊天界面
1、制作Nine-Patch图片
首先要确保已经为JDK配置了环境变量。
Android sdk目录下tools有draw9patch.bat文件,使用它来知足Nine-Patch图片。
双击draw9patch.bat文件打开,导航栏File-Open 9-patch将.png格式图片加载进来。拖动边线,设置图片边缘黑线。上边框和左边框表示要拉伸区域,下边框和右边框表示部分表示内容放置区域。
导航栏File-Save 9-patch保存图片.9.png。
2、使用到的方法
msgRecyclerView = (RecyclerView) findViewById(...);
adapter = new MsgAdapter(...);
adapter.notifyItemInseted(msgList.size() - 1);//通知适配器有新数据插入。刷新adapter
msgRecycler.scrollToPosition(msgList.size() - 1);//将RecyclerView定位到最后一行