文章目录
1.如何编写程序界面
As中提供了可视化的编辑器,允许我们使用拖拽的方式来编写布局,并能在视图上直接修改控件的属性,不过并不推荐大家使用这种方式来编写界面,因为可视化编辑工具并不利于你真正了解界面背后的实现原理。因此我们将采用最基本的方式去实现程序界面,即编写XML代码。
2.常用控件的使用方法
2.1TextView
用于在界面上显示一段文本信息
示例:
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"//文字的对齐方式属性
android:textColor="#00ff00"//文字颜色属性
android:textSize="30sp"//文字大小属性
android:text="This is textView"/*文字内容属性*/ />
2.2Button
在界面上添加一个按钮(用户交互控件,在程序中可以为其注册监听器来处理点击事件的逻辑)
示例:
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"//按钮的背景属性
android:text="Button"//按钮上显示的文字属性
android:textAllCaps="false"/*按钮上的字母默认均为大写,该属性可以避免将字母转为大写*/ />
2.3EditText
在界面上添加一个输入框(与用户交互控件,允许用户在控件里输入和编辑内容,并可以在程序中对这些内容进行处理)
示例:
<EditText
android:id="@+id/edit_text"
android:textColor="#00ff00"//输入文字的颜色属性
android:textSize="25sp"//输入文字的大小属性
android:maxLines="2"//指定EditText的最大行数为两行,当输入超过两行时,文本会向上滚动,而EditText不会再继续拉伸
android:hint="Type something here"//一段文本提示信息,当用户输入任意内容后自动消失
android:layout_width="match_parent"
android:layout_height="wrap_content" />
2.4ImageView
在界面上展示图片
示例:
<ImageView
android:id="@+id/image_view"
android:src="@drawable/fruit"//所展示的图片属性
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
2.5ProgressBar
在界面上显示一个进度条(默认为环形进度条,可以增加属性style="?android:progressBarStyleHorizontal"将进度条设置为长条形,之后还可以添加android:max="100"属性给进度条设置一个最大值)
示例:
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
2.6AlterDialog
在界面上弹出一个对话框,该对话框置顶于所有界面元素之上,能够屏蔽掉其它控件的交互能力(该控件与其他控件不同需要在代码中动态添加)
示例:
AlertDialog.Builder dialog=new AlertDialog.Builder(MainActivity.this);//创建AlterDialog实例
dialog.setTitle("This is title");//为AlterDialog设置标题内容
dialog.setMessage("Something important message");//为AlterDialog设置主要内容
dialog.setCancelable(false);//禁止使用Back键关闭
dialog.setPositiveButton("Ok", new DialogInterface.OnClickListener() {//为AlterDialog添加Positive按钮
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//"Ok"按钮的点击逻辑
}
});
dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {//为AlterDialog添加Negative按钮
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//"Cancel"按钮的点击逻辑
}
});
dialog.show();//显示AlterDialog
2.7控件的可见属性
所有的Android控件都具有该属性,可以通过android:visibility进行指定,可选值有3种:gone,visible和invisible。gone表示控件不可见且不占据任何屏幕空间;visible表示控件可见且会占据一定大小的屏幕空间;invisible表示控件不可见,但仍然占据原来大小的屏幕空间。我们还可以调用setVisibility()在代码中动态调整控件的可见属性。
3.Android的4大基本布局
3.1线性布局
LinearLayout又称作线性布局,是一种非常常用的布局。它可以使其中的所有控件线性排列。
android:orientation=“vertical”(竖向布局)也可设置为horizontal。(默认情况,横向布局)
该布局下还可以使用android:layout_weight属性来实现按百分比分配控件大小。
示例:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
3.2相对布局
RelativeLayout又称作相对布局,也是比较常用的布局。
相对布局共分两种情况:1.相对父布局布局;2.相对某一控件布局。
示例:
//相对父布局居中摆放
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button_1"
android:text="Button 1"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
//相对某一控件摆放在其右侧
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button_1"
android:text="Button 1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/button_2"
android:text="Button 2"
android:layout_toRightOf="@+id/button_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
3.3帧布局
FrameLayout又称作帧布局,这种布局没有方便的定位方式,所有的控件都会默认摆放在布局的右上角,会出现重叠的可能。不过可以用android:layout_gravity属性指定控件的对齐方式。
示例:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
3.4百分比布局
上面三个布局都是从Android1.0版本中就开始支持了。不过除了相对布局支持按照百分比分配控件大小,其它两种布局如果想要实现两个控件平分布局宽度的效果还是比较难以实现的,因此Android引入了一种全新的布局方式来解决此问题——百分比布局。
声名依赖 使用方式 github地址
https://github.com/JulienGenoud/android-percent-support-lib-sample
4.自定义控件
4.1引入布局
事实上我们可以重新创建一个布局,然后只需再活动布局里引入该布局即可,这样就可以很大程度上节约我们的代码量。
示例:
//新创建的布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button_back"
android:text="Back"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text_view"
android:text="This is title"
android:textSize="25sp"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<Button
android:id="@+id/button_setting"
android:text="Setting"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
//在活动中引入布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/my_layout" />
</LinearLayout>
4.2自定义控件
比如说上面我们新建的title布局,可以帮助我们节省布局文件中的代码量。不过细心的朋友可能会问,这个title布局里还有两个按钮,实际使用中这两个按钮的点击逻辑大多数情况下都是一样的,哪能否节省这部分处理点击逻辑的代码呢?我们这节知识就可以解决这个问题了。
示例:
//处理Title布局中点击事件代码的类
public class Title extends LinearLayout {
public Title(final Context context, @Nullable AttributeSet attrs) {//注意:这里选择重写Title方法时选择有两个参数的
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.my_layout,this);//加载布局,注意:这里只有两个参数
Button backButton=(Button) findViewById(R.id.button_back);
backButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
((Activity)getContext()).finish();
}
});
Button settingButton=(Button) findViewById(R.id.button_setting);
settingButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
//Setting按钮的逻辑代码
}
});
}
}
//加入自定义的控件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.temp02.Title//上面类的完整路径
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
5.ListView——滚动控件
由于手机屏幕的空间相对都比较有限,能够一次性在屏幕中展示的内容并不多,如果我们的程序中有大量的数据需要显示时就可以借助ListView来实现。它允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时原有数据则会滚动出屏幕。
5.1定制ListView界面
1.在活动的布局文件中加入ListView控件。
2.创建在ListView中显示的子布局。
3.我们还需要新建为子布局控件提供实质内容的java类。
4.定义我们自己ListView的适配器。
5.在活动中为ListView初始化内容并为其设置适配器。
示例(我们这里展示一个显示一系列水果的照片和名字的ListView):
//步骤一
<LinearLayout 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="wrap_content" />
</LinearLayout>
//步骤二
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/fruit_name"
android:gravity="center"
android:layout_marginLeft="10dp"//该属性表示控件于左间隔10dp的宽度
android:textSize="30sp"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
</LinearLayout>
//步骤三
public class Fruit {
String name;
int image;
public Fruit(String name,int image){
this.image=image;
this.name=name;
}
public String getName() {
return name;
}
public int getImage() {
return image;
}
}
//步骤四
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;//获取构造器中的子布局信息
public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {//注意:选择我们这里使用的构造器
super(context, resource, objects);
resourceId=resource;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit=getItem(position);
View view= LayoutInflater.from(parent.getContext()).inflate(resourceId,parent,false);//加载子布局
TextView fruitName=(TextView) view.findViewById(R.id.fruit_name);//获取子布局中控件的实例
ImageView fruitImage=(ImageView) view.findViewById(R.id.fruit_image);
fruitName.setText(fruit.getName());//为控件加入实质内容
fruitImage.setImageResource(fruit.getImage());
return view;
}
}
//步骤五
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();
ListView listView=(ListView) findViewById(R.id.list_view);
FruitAdapter adapter=new FruitAdapter(MainActivity.this,R.layout.item_layout,fruitList);//获取适配器实例
listView.setAdapter(adapter);//为ListView设置适配器
}
private void initFruits(){//初始化ListView中的内容
for(int i=0;i<50;i++){
Fruit apple=new Fruit("apple",R.drawable.apple);//这里为了方便只弄了一种水果
fruitList.add(apple);
}
}
}
5.2提升ListView的运行效率
目前我们的ListView的运行效率是很低的,因为在适配器中的getView()方法中,每次都要将布局内容重新加载一遍并且每次都要重复获取控件的实例。因此我们的优化方向就有两种:1.缓存布局内容;2.缓存控件实例。
示例:
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
super(context, resource, objects);
resourceId=resource;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit=getItem(position);
View view;
ViewHodler viewHodler=new ViewHodler();
if(convertView==null){//我们这里用方法参数中的convertView来缓存布局内容
view= LayoutInflater.from(parent.getContext()).inflate(resourceId,parent,false);
viewHodler.fruitImage=(ImageView) view.findViewById(R.id.fruit_image);//用ViewHolder缓存控件实例
viewHodler.fruitName=(TextView) view.findViewById(R.id.fruit_name);
view.setTag(viewHodler);//将ViewHolder保存在view中
}else{
view=convertView;//直接获取缓存好的View
viewHodler=(ViewHodler) view.getTag();//直接从View中取出ViewHolder
}
viewHodler.fruitName.setText(fruit.getName());
viewHodler.fruitImage.setImageResource(fruit.getImage());
return view;
}
class ViewHodler{//新建类用来缓存控件
TextView fruitName;
ImageView fruitImage;
}
}
5.3ListView的点击事件
直接在活动中为ListView注册监听器即可(相当于给ListView中的每个子布局注册监听器)
示例:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Fruit fruit=fruitList.get(i);
Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();//展示当前水果名
}
});
6.RecyclerView——更强大的滚动控件
我们上面所讲的ListView还是存在许多缺点的。比如说如果我们不使用技巧来提升它的运行效率,那么ListView的性能就会很差,还有ListView的扩展性也不够好,它只能实现纵向的滚动效果。因此,Android提供了一个更强大的滚动控件——RecyclerView。
6.1RecyclerView的基本用法
1.RecyclerView属于新增控件,因此需要先添加依赖
2.在活动的布局文件中加入RecyclerView控件。
3.创建在RecyclerView中显示的子布局。
4.我们还需要新建为子布局控件提供实质内容的java类。
5.定义我们自己recyclerView的适配器。
6.在活动中为RecyclerView初始化内容并为其设置管理于适配器。
示例(我们这里还是展示一个显示一系列水果的照片和名字的RecyclerView):
//步骤一
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.1.0'
}
//步骤二
<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="wrap_content" />
</LinearLayout>
//步骤三
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/fruit_name"
android:gravity="center"
android:layout_marginLeft="10dp"
android:textSize="30sp"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
</LinearLayout>
//步骤四
public class Fruit {
String name;
int image;
public Fruit(String name,int image){
this.image=image;
this.name=name;
}
public String getName() {
return name;
}
public int getImage() {
return image;
}
}
//步骤五
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> fruitList=new ArrayList<>();
public FruitAdapter(List<Fruit> list){//定义适配器
fruitList=list;
}
public class ViewHolder extends RecyclerView.ViewHolder {//缓存控件实例提高运行效率
TextView fruitName;
ImageView fruitImage;
public ViewHolder(@NonNull View itemView) {
super(itemView);
fruitImage=(ImageView) itemView.findViewById(R.id.fruit_image);
fruitName=(TextView) itemView.findViewById(R.id.fruit_name);
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {//加载布局内容,获取控件实例
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout,parent,false);
ViewHolder viewHolder=new ViewHolder(view);
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {//为控件添加实质内容
Fruit fruit=fruitList.get(position);
holder.fruitName.setText(fruit.getName());
holder.fruitImage.setImageResource(fruit.getImage());
}
@Override
public int getItemCount() {//返回RecyclerView中数据的长度
return fruitList.size();
}
}
//步骤六
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 manager=new LinearLayoutManager(this);//获取manager实例,并为RecyclerView设置manager
recyclerView.setLayoutManager(manager);
FruitAdapter adapter=new FruitAdapter(fruitList);//获取适配器实例,并为RecyclerView设置适配器
recyclerView.setAdapter(adapter);
}
private void initFruits(){//给RecyclerView初始化内容
for(int i=0;i<50;i++){
Fruit apple=new Fruit("apple",R.drawable.apple);
fruitList.add(apple);
}
}
}
6.2实现横向滚动和网格状布局
实现横向滚动还是比较简单的,这里就要借助我们的manager了
示例:
RecyclerView recyclerView=(RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager manager=new LinearLayoutManager(this);
manager.setOrientation(RecyclerView.HORIZONTAL);//将manager设置为横向即可
recyclerView.setLayoutManager(manager);
FruitAdapter adapter=new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
同理实现网格状布局关键点也是在manager上
示例:
RecyclerView recyclerView=(RecyclerView) findViewById(R.id.recycler_view);
GridLayoutManager manager=new GridLayoutManager(this,2);//将manager换为这种形式,第二个参数就是列数
recyclerView.setLayoutManager(manager);
FruitAdapter adapter=new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
6.3RecyclerView的点击事件
这里也是RecyclerView比ListView要好的地方,ListView只能给子布局设置监听器,而RecyclerView则可以给子布局设置监听器,也可以给子布中的控件设置监听器。(RecyclerView的监听器是在适配器中注册的)
示例:
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> fruitList=new ArrayList<>();
public FruitAdapter(List<Fruit> list){
fruitList=list;
}
public class ViewHolder extends RecyclerView.ViewHolder {
View view;
TextView fruitName;
ImageView fruitImage;
public ViewHolder(@NonNull View itemView) {
super(itemView);
view=itemView;//获取子布局实例
fruitImage=(ImageView) itemView.findViewById(R.id.fruit_image);
fruitName=(TextView) itemView.findViewById(R.id.fruit_name);
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, int viewType) {
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout,parent,false);
final ViewHolder viewHolder=new ViewHolder(view);
viewHolder.view.setOnClickListener(new View.OnClickListener() {//给子布局注册监听器
@Override
public void onClick(View view) {
int position=viewHolder.getAdapterPosition();
Fruit fruit=fruitList.get(position);
Toast.makeText(parent.getContext(),fruit.getName(),Toast.LENGTH_SHORT).show();
}
});
viewHolder.fruitName.setOnClickListener(new View.OnClickListener() {//给子布局中的控件注册监听器
@Override
public void onClick(View view) {
//点击事件的逻辑
}
});
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Fruit fruit=fruitList.get(position);
holder.fruitName.setText(fruit.getName());
holder.fruitImage.setImageResource(fruit.getImage());
}
@Override
public int getItemCount() {
return fruitList.size();
}
}
6.4关于RecyclerView多个布局导致item数据错乱的问题
recyclerview在采用多种布局的情况下,在onBindViewHolder绑定的时候,把不会显示的布局用View.GONE隐藏起来,看似没有问题!但是上下滑动的时候会发生item显示错乱的问题!这是由于RecyclerView的onBindViewHolder()方法,只有在getItemViewType()返回类型不同时才会调用,这点是跟ListView的getView()方法不同的地方,所以如果频繁改变子布局的类型,滑动后就会发生数据错乱的问题,因此如果想要每次都调用onBindViewHolder()刷新item数据,就要重写getItemViewType(),让其返回position,否则很容易产生数据错乱的现象。
解决方法示例:
@Override
public int getItemViewType(int position) {//在适配器中添加
return position;
}