《第一行代码》学习笔记——第三章 UI开发
常用控件
TextView
示例代码:
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="24sp"
android:textColor="#00ff00"
android:text="This is TextView"/>
android:gravity指定文字对齐方式,可选top,bottom,left,right,center等。可以用“|”来同时指定多个值。
android:textSize指定文字的大小
android:textColor指定文字的颜色
Button
示例代码:
activity_main.xml
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="Button" />
MainActivity.java
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
android:textAllCaps是否对所有因为字母自动大写
EditText
示例代码:
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something here"
android:maxLines="2"/
android:hint指定一段提示性文字
android:maxLines指定EditText最大行数
ImageView
示例代码:
<ImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/img_1"/>
ProgressBar
ProgressBar用于在界面上显示一个进度条。
示例代码:
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible"/>
可以通过在代码中设置ProgressBar的visibility来显示和隐藏进度。
通过设置style修改ProgressBar的样式,如下为水平进度条:
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:max="100"/>
AlertDialog
AlertDialog可以在当前界面弹出一个对话框,这个对话框置顶于所有界面元素之上,能够屏蔽掉其他控件的交互能力。
示例代码:
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("This is Dialog")
.setMessage("Something important")
.setCancelable(false)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.show();
ProgressDialog
ProgressDialog会在对话框中显示一个进度条。
示例代码:
ProgressDialog dialog = new ProgressDialog(MainActivity.this);
dialog.setTitle("This is ProgressDialog");
dialog.setMessage("Loading...");
dialog.setCancelable(true);
dialog.show();
可以使用ProgressDialog的dismiss()方法关闭对话框。
4种基本布局
线性布局
LinearLayout又称作线性布局,该布局会将它所包含的控件在线性方向上依次排列。通过设置android:orientation属性指定排序方向,vertical或者horizontal。
相对布局
RelativeLayout又称作相对布局,它可以通过相对定位的方式让控件出现在布局的任何位置。
相对父布局进行定位:
android:layout_alignParentLeft:左对齐
android:layout_alignParentRight:右对齐
android:layout_ailgnParentTop:上对齐
android:layout_alignParentBottom:下对齐
android:layout_centerInParent:居中
相对控件进行定位:
android:layout_above:让该控件位于另一个控件上方
android:layout_below:让该控件位于另一个控件下方
android:layout_toLeftOf:让该控件位于另一个控件左侧
android:layout_toRightOf:让该控件位于另一个控件右侧
android:layout_alignLeft:让该控件的左边缘和另一个控件的左边缘对齐
android:layout_alignRight:让该控件的右边缘和另一个控件的右边缘对齐
android:layout_alignTop:让该控件的上边缘和另一个控件的上边缘对齐
android:layout_alignBottom:让该控件的下边缘和另一个控件的下边缘对齐
帧布局
FrameLayout又称作帧布局,该布局没有方便的定位方式,所有控件都会默认摆放在布局的左上角。
百分比布局
百分比布局只为FrameLayout和RelativeLayout进行了功能扩展,提供了PercentFrameLayout和PercentRelativeLayout两个新的布局。
该布局不在系统库中,需要引入:
implementation 'com.android.support:percent:30.0.1'
创建自定义控件
引入布局
- 新建布局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="@color/colorPrimary">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:background="@color/colorAccent"
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="@color/colorAccent"
android:text="Edit"
android:textColor="#fff"/>
</LinearLayout>
- 在activity_main.xml中引入布局:
<include layout="@layout/title"/>
- 在Activity中隐藏自系统自带的标题栏:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
}
}
创建自定义控件
- 新建TitleLayout类:
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
}
}
- 为标题栏中的Button注册事件:
public TitleLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
Button titleBack = findViewById(R.id.title_back);
Button titleEdit = findViewById(R.id.title_edit);
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();
}
});
}
- 在Activity的xml中使用该布局:
<com.lhh.uicustomviews.TitleLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
ListView
简单用法
- 在xml中使用ListView控件:
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- 在Activity中设置ListView:
private ListView listView;
private String[] data = {
"Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape", "Pineapple", "Strawberry",
"Cherry", "Mango"
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayAdapter<String> adapter
= new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, data);
listView = findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
定制ListView界面
- 新建Fruit实体类:
public class Fruit {
private String name;
private int imageId;
public Fruit(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
public String getName() {
return name;
}
public int getImageId() {
return imageId;
}
}
- 为ListView新建子项布局fruit_item.xml:
<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:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"/>
</LinearLayout>
- 新建自定义适配器FruitAdapter:
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(getContext()).inflate(resourceId, parent, false);
ImageView fruitImage = view.findViewById(R.id.fruit_image);
TextView fruitName = view.findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
}
- 在Activity中设置ListView:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FruitAdapter adapter = new FruitAdapter(this, R.layout.fruit_item, fruitList);
listView = findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
提升ListView的运行效率
ListView控件的使用难度在于其有许多细节可以优化,其中运行效率就是很重要的一点。当前在getView()方法中,每次都将布局重新加载了一遍,所以运行效率很低。
- 复用View。getView()方法中的converView参数是用于将之前加载好的布局进行缓存,以便之后可以重用:
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
} else {
view = convertView;
}
ImageView fruitImage = view.findViewById(R.id.fruit_image);
TextView fruitName = view.findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
- 缓存控件实例。在getView()方法中每次还是需要调用findViewById()方法来获取控件实例。可以借助ViewHolder来进行优化:
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull 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 = view.findViewById(R.id.fruit_image);
viewHolder.fruitName = view.findViewById(R.id.fruit_name);
view.setTag(viewHolder);
} else {
view = convertView;
viewHolder = (ViewHolder)view.getTag();
}
viewHolder.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitName.setText(fruit.getName());
return view;
}
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
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();
}
});
RecyclerView
RecyclerView是增强版的ListView。实现了ListView同样的效果,并且优化了ListView的许多不足。
使用RecyclerView需要引入依赖:
implementation 'androidx.recyclerview:recyclerview:1.1.0'
基本使用
- 新建Fruit实体类和fruit_item.xml子项布局,同上
- 新建适配器FruitAdapter:
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> fruitList;
public FruitAdapter(List<Fruit> fruitList) {
this.fruitList = fruitList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Fruit fruit = fruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount() {
return fruitList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView fruitImage;
TextView fruitName;
public ViewHolder(@NonNull View itemView) {
super(itemView);
fruitImage = itemView.findViewById(R.id.fruit_image);
fruitName = itemView.findViewById(R.id.fruit_name);
}
}
}
- 在Activity中设置RecyclerView:
private List<Fruit> fruitList = new ArrayList<>();
private RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
FruitAdapter fruitAdapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(fruitAdapter);
}
实现横向滚动
- 修改fruit_item.xml以适配横向滚动:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="100dp"
android:layout_height="wrap_content">
<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>
- 在Activity中设置RecyclerView为横向滚动
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(RecyclerView.HORIZONTAL);
recyclerView.setLayoutManager(layoutManager);
实现瀑布流
- 修改fruit_item.xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<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="left"
android:layout_marginTop="10dp"/>
</LinearLayout>
- 在Activity中配置RecyclerView的LayoutManager:
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
RecyclerView的点击事件
RecyclerView没有提供类似setOnItemClickListener()的注册监听的方法。需要自己给子项具体的View注册点击事件。虽然比ListView要复杂一点,但因此也可以为子项的所有元素各自添加事件。
- 修改FruitAdapter.ViewHolder:
static class ViewHolder extends RecyclerView.ViewHolder {
View fruitView;
ImageView fruitImage;
TextView fruitName;
public ViewHolder(@NonNull View itemView) {
super(itemView);
fruitView = itemView;
fruitImage = itemView.findViewById(R.id.fruit_image);
fruitName = itemView.findViewById(R.id.fruit_name);
}
}
- 修改FruitAdapter的onCreateViewHolder()方法:
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
final ViewHolder viewHolder = new ViewHolder(view);
viewHolder.fruitView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = viewHolder.getAdapterPosition();
Fruit fruit = fruitList.get(position);
Toast.makeText(v.getContext(), "You clicked view " + fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
viewHolder.fruitImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = viewHolder.getAdapterPosition();
Fruit fruit = fruitList.get(position);
Toast.makeText(v.getContext(), "You clicked image " + fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
return viewHolder;
}