本篇主要涉及Android中UI开发和碎片的使用。
一、 UI开发
(一) 常见控件的使用
1. TextView
android:gravity
指定文字对其方式,可选值有top、bottom、left、right、center等。
2. Button
- 匿名类方式注册监听器
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 相应逻辑
}
});
- 实现接口方式注册监听器
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(bundle savedInstanceState) {
...
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
// 相应逻辑
break;
default:
break;
}
}
}
3. EditText
android:hint
设置提示性文本android:maxLines
设置最大行数获取输入的内容:
String inputText = editText.getText().toString();
4. ImageView
android:src="@drawable/img_1"
指定图片- 动态更改图片:
imageView.setImageResource(R.drawable.img_2);
5. ProgressBar
android:visibility
设置控件的可见属性,默认visible
表示可见,invisible
表示不可见但占据着原来的位置(透明状态),gone
表示不可见且不占用任何屏幕空间。- 通过代码设置可见属性:
if (progressBar.getVisibility() ==View.VISIBLE) {
progressBar.setVisibility(View.GONE);
}
- 通过style属性可以指定不同样式。
style="?android:attr/progressBarStyleHorizontal"
- 通过
android:max="100"
给进度条设置一个最大值,然后可在代码中动态更改进度:
int progress = progressBar.getProgress();
progress = progress + 10;
progressBar.setProgress(progress);
6. AlertDialog
- 通过AlertDialog.Builder创建一个AlertDialog实例,然后可以设置标题、内容、可否取消等属性,接下来调用
setPositiveButton()
方法为对话框设置确定按钮的点击事件,调用setNegativeButton()
方法设置取消按钮的点击事件,最后调用show()
方法将对话框显示出来。
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
dialog.setTitle("这是标题");
dialog.setMessage("重要内容或警告信息");
dialog.setCancelable(false);
dialog.setPositiveButton("OK", new dialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
dialog.setNegativeButton("Cancel", new DialogInterface.OnClickLister() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
dialog.show();
7. ProgressDialog
- 与AlertDialog类似,也是构建一个ProgressDialog对象,然后设置标题、内容、可否取消等,最后通过
show()
方法显示出来。 - 调用
dismiss()
方法关闭对话框;
(二) 四种基本布局
1. 线性布局 (LinearLayout)
android:orientation
指定排列方向,可选vertical或horizontal;android:layout_gravity
指定控件在布局中的对齐方式,可选值有top、bottom、center_viertical、left、right等;android:layout_weight
可以按比例控制控件大小;
2. 相对布局 (RelativeLayout)
android:layout_alignParentLeft
、android:layout_alignParentRight
、android:layout_alignParentTop
、android:layout_alignParentBottom
、
android:layout_centerInParent
属性设置控件在父控件中的位置。android:above
、android:below
、android:toLeftOf
、android:toRightOf
设置控件间的相对位置。
3. 帧布局 (FrameLayout)
- 所有控件默认摆放在布局左上角,可使用
android:layout_gravity
设置对齐方式。
4. 百分比布局
- 包括PercentFrameLayout和PercentRelativeLayout,包含在
com.android.support:percent
库中。 app:layout_widthPercent
、app:layout_heightPercent
可直接指定控件在布局中所占百分比。
<Button
android:id="@+id/button1"
android:text="Button 1"
android:layout_gravity="left|top"
app:layout_widthPercent="50%"
app:layout_heightPercent="50%" />
(三) 自定义控件
1. 引入布局
构建一个布局文件,在主活动的布局中使用 <include layout="@layout/title" />
引入。
2. 创建自定义控件
- 新建TitleLayout继承自LinearLayout,重写构造方法,借助LayoutInflater对自定义布局进行动态加载。
from(context)
方法构建出LayoutInflater对象inflate(要加载的布局文件id, 父布局)
进行动态加载
- 加入相应逻辑。
- 在布局文件中添加,需要指明完整类名。
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);
titleBack.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((Activity) getContext()).finish();
});
}
}
<com.example.uicostomviews.TitleLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
(四) ListView
1. 基本用法
借助适配器ArrayAdapter将数据传递给ListView:
- ArrayAdapter构造函数中传入三个参数 (当前上下文、ListView子项布局id、数据)。
- 调用ListView的
setAdapter()
方法将适配器传入。
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);
2. 定制界面
(1) 定义一个实体类作为Adapter的适配类型
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;
}
}
(2) 为ListView子项建立自定义布局
<!-- fruit_item.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<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_verticlal"
android:layout_marginLeft="10dp" />
</LinearLayout>
(3) 创建自定义适配器
重写构造函数和 getView()
方法,getView()会在每个子项滚动到屏幕内时被调用。
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
// 重写构造函数
public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) {
super(context, textViewResourceId, objects);
resourceId = textViewResourceId;
}
// 重写getView()方法
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position); // 获取当前项的Fruit实例
View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false); // 第三个参数表示只让父布局的Layout属性生效,但不为这个View添加父布局
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;
}
(4) 在MainActivity中创建Adapter对象并传递给ListView
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
...
initFruit(); // 初始化水果数据的方法
FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
ListView listView = (ListView) findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
private void initFruits() {
...
Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
fruitList.add(apple);
...
}
}
3. 优化
- 解决重复加载布局:
getView()
中convertView参数会将之前加载好的布局进行缓存,便于之后重用。 - 解决重复findViewById获取控件实例:新增内部类ViewHolder对控件实例进行缓存。
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;
}
4. 点击事件
使用 setOnItemClickListener()
方法注册监听器,点击子项时回调 onItemClick()
方法,重写此方法加入点击事件的处理逻辑。
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
1. 基本用法
- 新建适配器FruitAdapter继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder。
- 定义一个内部类ViewHolder继承自Recycler.ViewHolder,构造函数传入一个View参数,通过findViewById获取布局中控件实例;
- 适配器的构造函数用于将数据数据源传进来;
- 重写
onCreateViewHolder()
、onBindViewHolder()
、getItemCount()
这三个方法;
- MainActivity中创建LinearLayoutManager对象,并调用
setLayoutMangager()
设置到RecyclerView中,再创建适配器实例并调用setAdapter()
完成适配器设置。
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();
}
}
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protect void onCreate(Bundle savedInstanceState) {
...
initFruits(); // 初始化水果数据
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearManager(this);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
private void initFruits(){
// 将水果数据添加到fruitList中
}
}
2. 横向滚动、网格布局和瀑布流布局
(1) 横向滚动
- 要实现横向滚动,需把fruit_item里的元素改为垂直排列。
- 调用LinearLayoutManager的
setOrientation()
来设置布局排列方向。
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
(2) 网格布局
- 使用GridLayoutManager,构造函数接收两个参数:(Context, 列数) 。
GridLayoutManager layoutManager = new GridLayoutManager(this, 2); // 两列的网格布局
recyclerView.setLayoutManager(layoutManager);
(3) 瀑布流布局
- 使用StaggeredGridLayoutManager,构造函数接收两个参数:(列数, 排列方向) 。
StaggeredGridLayoutManager layoutManager = new StaggeredLayoutManager(3, StaggeredGridLayoutManager.VERTICAl); // 三列、纵向排列
recyclerView.setLayoutManager(layoutManager);
3. 点击事件
需要在 onCreateViewHolder()
中自己给子项具体的View去注册点击事件。
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHoder> {
...
@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.fruitImage.setOnclickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getAdapterPosition();
Fruit fruit = mFruitList.get(position);
Toast.makeText(v.getContext(), "点击了图片" + fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
return holder;
}
...
}
二、 Fragment
(一) 碎片的使用
1. 基本用法
- 创建碎片的布局文件;
- 新建类继承自Fragment,重写
onCreateView()
方法; - 在主活动的布局文件中添加
<fragment>
标签,需要通过android:name
指明添加的碎片完整类名(带包名)。
public class MyFragment extends Fragment {
@Override
public View onCreateView(LayoutInflate inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.my_fragment, container, false);
return view;
}
}
<fragment
android:id="@+id/my_fragment"
android:name="com.example.fragmenttest.MyFragment"
android:layoutwidth="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
2. 动态添加碎片
(1) 创建待添加碎片实例;
(2) 调用getSupportFragmentManager()
方法获取FragmentManager;
(3) 调用 beginTransaction()
开启一个事务;
(4) 调用 replace()
方法向容器内添加或替换碎片,需传入 (容器id, 碎片实例);
(5) 调用 commit()
方法提交事务。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
replaceFragment(new AnotherFragment()); // 替换为另一个碎片
}
});
replaceFragment(new MyFragment()); // 初始设置为MyFragment
}
private void repalceFragment(Fragment fragment) {
FragmentManager fragmentmanamger = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.repalce(R.id.right_layout, fragment);
transaction.commit();
}
}
3. 碎片中模拟返回栈
调用 addToBackStack()
方法将事务添加到返回栈中,它接收一个名字用于描述返回栈状态,一般传入null即可,这样按下Back键会回到上一个Fragment界面。
FragmentManager fragMentmanager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout, fragment);
transaction.addToBackStack(null); // 添加到返回栈
transaction.commit();
4. 碎片和活动间进行通信
- 活动中调用碎片里的方法:使用FragmentManager的
findFragmentById
获取碎片实例,即可调用其中方法。 - 碎片中调用活动里的方法:使用
getActivity()
得到和当前碎片关联的活动实例,即可调用其中方法。
MyFragment myFragment = (MyFragment) getFragmentManager().findFragmentById(R.id.my_fragment); // 活动中获取碎片实例
MainActivity activity = (MainActivity) getActivity(); // 碎片中获取活动实例
(二) 碎片的生命周期
与活动生命周期类似,并提供了一些的附加的回调方法:
onAttach()
:当碎片和活动建立关联时调用。onCreateView()
:为碎片创建视图(加载布局)时调用。onActivityCreated()
:确保与碎片关联的活动一定已创建完毕时调用。onDestroyView()
:当与碎片关联的视图被移除时调用。onDetach()
:当碎片和活动解除关联时调用。
碎片中也可以通过 onSaveInstanceState()
方法保存数据,保存的数据在 onCreate()
、onCreateView()
、onActivityCreated()
中都可以得到。
(三) 动态加载布局技巧
1. 使用限定符
- 屏幕大小:small、normal、large、xlarge
- 屏幕分辨率:ldpi、mdpi、hdpi、xhdpi、xxhdpi
- 方向:land、port
2. 使用最小宽度限定符(Smallest-width Qualifier)
res目录下新建layout-sw600dp文件夹,在其中新建acticity_main.xml布局。
- 屏幕宽度大于600dp的设备:加载layout-sw600dp/activity_main布局
- 屏幕宽度小于600dp的设备:仍会加载默认的layout/activity_main布局。