天津建设工程信息网如何注册,wordpress手动数据库优化,做视频网站视频文件都存放在哪里,免费简历制作网站推荐下拉框
此小节介绍下拉框的用法以及适配器的基本概念#xff0c;结合对下拉框Spinner的使用说明分别阐述数组适配器ArrayAdapter、简单适配器SimpleAdapter的具体用法与展示效果。
下拉框控件Spinner
Spinner是下拉框控件#xff0c;它用于从一串列表中选择某项#xff0…下拉框
此小节介绍下拉框的用法以及适配器的基本概念结合对下拉框Spinner的使用说明分别阐述数组适配器ArrayAdapter、简单适配器SimpleAdapter的具体用法与展示效果。
下拉框控件Spinner
Spinner是下拉框控件它用于从一串列表中选择某项其功能类似于单选按钮的组合。下拉列表的展示方法有两种一种是在当前下拉框的正下方弹出列表框此时要把spinnerMode属性设置为dropdown下面是XML文件中采取下拉模式的Spinner标签例子
Spinnerandroid:idid/sp_dropdownandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:spinnerModedropdown /另一种是在页面中部弹出列表对话框此时要把spinnerMode属性设置成dialog下面是XML文件中采取对话框模式的Spinner标签例子
Spinnerandroid:idid/sp_dropdownandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:spinnerModedialog /此外在Java代码中Spinner还可以调用下列4个方法。
setPrompt设置标题文字。注意对话框模式才显示标题下拉模式不显示标题。setAdapter设置列表项的数据适配器。setSelection设置当前选中哪项。注意该方法要在setAdapter方法后调用。setOnItemSelectedListener设置下拉列表的选择监听器该监听器要实现接口AdapterView.OnItemSelectedListener。 下面是初始化下拉框并设置选择监听器的代码例子
// 初始化下拉模式的列表框
private void initSpinnerForDropdown() {// 声明一个下拉表的数组适配器ArrayAdapterString starAdapter new ArrayAdapter(this, R.layout.item_select, starArray);// 从布局文件中获取名为sp_dropdown的下拉框Spinner sp_dropdown findViewById(R.id.sp_dropdown);// 设置下拉框的标题。对话框模式才显示标题下拉模式不显示标题sp_dropdown.setPrompt(请选择行星);// 设置下拉框数组适配器sp_dropdown.setAdapter(starAdapter);// 设置下拉框默认选择第一个sp_dropdown.setSelection(0);// 给下拉框注册监听器一旦用户选择某一项就出发监听器的onItemSelected方法sp_dropdown.setOnItemSelectedListener(new MySelectedListener());
}接下来观察两种下拉列表的界面效果运行App一开始的下拉框效果如下图 在下拉模式页面SpinnerDropdownActivity.java单击下拉框六大行星的列表在下拉框正下方展开。点击某项后列表框消失同时下拉框中的文字变为刚选中的行星名称。展开效果如下图 将spinnerMode属性设置成dialog点击下拉框则下拉框会弹出如下图
数组适配器ArrayAdapter
上一小节在演示下拉框时调用了setAdapter方法设置列表适配器。这个适配器好比一组数据的加工流水线你丢给它一大把糖果六大行星的原始数据适配器先按顺序排列糖果对应行星数组starArray,然后拿来制作好的包装对应每个列表项的布局文件item_select.xml把糖果往里一塞出来的便是一个个精美的糖果盒界面上排布整齐的列表框。这个流水线可以做得很复杂也可以做得简单一些最简单的流水线就是之前演示用到的数组适配器ArrayAdapter。 ArrayAdapter主要用于每行列表只展示文本的情况实现过程分成下列3个步骤
编写列表项的XML文件内部布局只有一个TextView标签示例如下
TextView xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:idid/tv_nameandroid:layout_widthmatch_parentandroid:layout_height50dpandroid:singleLinetrueandroid:gravitycenterandroid:textSize17spandroid:textColor#0000ff /调用ArrayAdapter的构造方法填入待展现的字符串数组以及列表项的包装盒即XML文件R.layout.item_select。构造方法的调用代码示例如下
// 声明一个下拉列表的数组适配器
ArrayAdapterString starAdapter new ArrayAdapterString(this, R.layout.item_select, starArray);调用下拉框控件的setAdapter方法传入步骤2得到的适配器实例代码如下
sp_dialog.setAdapter(starAdapter); // 设置下拉框的数组适配器经过以上3个步骤先由ArrayAdapter明确原料糖果的分拣过程与包装方式再由下拉框调用sertAdapter方法发出开工指令适配器便会一个个包装好的糖果盒输出到界面。
简单适配器SimpleAdapter
ArrayAdapter只能显示文本列表显然不够美观有时还想给列表加上图标比如希望显示六大行星的天文影像。这是简单适配器SimpleAdapter就排上用场了它允许在列表项中同时展示文本与图片。 SimpleAdapter的实现过程略微复杂因为它的原料需要更多信息。例如原料不但有糖果还有贺卡这样就得把一大袋糖果和一大袋贺卡送进流水线适配器每次拿一颗糖果和一张贺卡把糖果与贺卡按规定塞进包装盒。对于SimpleAdapter的构造方法来说第二个参数Map容器放的是原料糖果与贺卡第三个参数放的是包装盒第四个参数放的是糖果袋与贺卡袋的名称第五个参数放的是包装盒里塞糖果的位置与塞贺卡的位置。 下面是下拉框控件使用简单适配器的示例代码
// 初始化下拉框演示简单适配器
private void initSpinnerForSimpleAdapter() {// 声明一个映射对象的列表用于保存行星的图标与名称配对信息ListMapString, Object list new ArrayListMapString, Object();// iconArray是行星的图标数组starArray是行星的名称数组for (int i 0; i iconArray.length; i) {MapString, Object item new HashMapString, Object();item.put(icon, iconArray[i]);item.put(name, starArray[i]);list.add(item); // 把行星图标与名称的配对映射添加到列表}// 声明一个下拉列表的简单适配器其中指定了图标与文本两组数据SimpleAdapter starAdapter new SimpleAdapter(this, list,R.layout.item_simple, new String[]{icon, name},new int[]{R.id.iv_icon, R.id.tv_name});// 设置简单适配器的布局样式starAdapter.setDropDownViewResource(R.layout.item_simple);// 从布局文件中获取名叫sp_icon的下拉框Spinner sp_icon findViewById(R.id.sp_icon);sp_icon.setPrompt(请选择行星); // 设置下拉框的标题sp_icon.setAdapter(starAdapter); // 设置下拉框的简单适配器sp_icon.setSelection(0); // 设置下拉框默认显示第一项// 给下拉框设置选择监听器一旦用户选中某一项就触发监听器的onItemSelected方法sp_icon.setOnItemSelectedListener(new MySelectedListener());
}以上代码中简单适配器使用的包装盒名为R.layout.item_simple它的布局内容如下
LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationhorizontal!-- 这是展示行星图标的ImageView --ImageViewandroid:idid/iv_iconandroid:layout_width0dpandroid:layout_height50dpandroid:layout_weight1 /!-- 这是展示行星名称的TextView --TextViewandroid:idid/tv_nameandroid:layout_width0dpandroid:layout_heightmatch_parentandroid:layout_weight3android:gravitycenterandroid:textColor#ff0000android:textSize17sp /
/LinearLayout运行App一开始显示的是默认选项的图片和文字如下图 点击下拉框页面中央弹出六大行星的列表对话框可见列表框的各项也一齐展示了行星的图标及其名称如下图
列表类视图
此节介绍列表类视图怎样结合基本适配器展示视图阵列包括基本适配器BaseAdapter的用法、列表视图ListView的用法及其常见问题的解决、网络视图GridView的用法及其拉伸模式说明。
基本适配器BaseAdapter
由上一节的介绍可知数组适配器适用于纯文本的列表数据简单适配器适用于带图标的列表数据。然而在实际应用中常常有更复杂的列表比如每个列表项存在3个以上的控件这种情况即便是简单适配器也很吃力而且不易扩展。为此Android提供了一种适应性更强的基本适配器BaseAdapter该适配器允许开发者在别的代码文件中编写操作代码大大提高了代码的可读性和可维护性。 从BaseAdapter派生的数据适配器主要实现下面5个方法
构造方法指定适配器需要处理的数据集合。getCount获取列表项的个数。getItem获取列表项的数据。getItemId获取列表项的编号。getView获取每项的展示视图并对每项的内部控件进行业务处理。
下面以下拉框控件为载体演示如何操作BaseAdapter具体编码过程分为3步
编写列表项的布局文件示例代码如下
LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationhorizontal!-- 这是显示行星图片的图像视图 --ImageViewandroid:idid/iv_iconandroid:layout_width0dpandroid:layout_height80dpandroid:layout_weight1android:scaleTypefitCenter /LinearLayoutandroid:layout_width0dpandroid:layout_heightmatch_parentandroid:layout_weight3android:layout_marginLeft5dpandroid:orientationvertical!-- 这是显示行星名称的文本视图 --TextViewandroid:idid/tv_nameandroid:layout_widthmatch_parentandroid:layout_height0dpandroid:layout_weight1android:gravityleft|centerandroid:textColorcolor/blackandroid:textSize20sp /!-- 这是显示行星描述的文本视图 --TextViewandroid:idid/tv_descandroid:layout_widthmatch_parentandroid:layout_height0dpandroid:layout_weight2android:gravityleft|centerandroid:textColorcolor/blackandroid:textSize13sp //LinearLayout
/LinearLayout写个新的适配器继承BaseAdapter实现对列表的管理操作示例代码如下
public class PlanetBaseAdapter extends BaseAdapter {private Context mContext; // 声明一个上下文对象private ListPlanet mPlanetList; // 声明一个行星信息列表// 行星适配器的构造方法传入上下文与行星列表public PlanetBaseAdapter(Context context, ListPlanet planet_list) {mContext context;mPlanetList planet_list;}// 获取列表项的个数public int getCount() {return mPlanetList.size();}// 获取列表项的数据public Object getItem(int arg0) {return mPlanetList.get(arg0);}// 获取列表项的编号public long getItemId(int arg0) {return arg0;}// 获取指定位置的列表项视图public View getView(final int position, View convertView, ViewGroup parent) {ViewHolder holder;if (convertView null) { // 转换视图为空holder new ViewHolder(); // 创建一个新的视图持有者// 根据布局文件item_list.xml生成转换视图对象convertView LayoutInflater.from(mContext).inflate(R.layout.item_list, null);holder.iv_icon convertView.findViewById(R.id.iv_icon);holder.tv_name convertView.findViewById(R.id.tv_name);holder.tv_desc convertView.findViewById(R.id.tv_desc);convertView.setTag(holder); // 将视图持有者保存到转换视图当中} else { // 转换视图非空// 从转换视图中获取之前保存的视图持有者holder (ViewHolder) convertView.getTag();}Planet planet mPlanetList.get(position);holder.iv_icon.setImageResource(planet.image); // 显示行星的图片holder.tv_name.setText(planet.name); // 显示行星的名称holder.tv_desc.setText(planet.desc); // 显示行星的描述holder.iv_icon.requestFocus();return convertView;}// 定义一个视图持有者以便重用列表项的视图资源public final class ViewHolder {public ImageView iv_icon; // 声明行星图片的图像视图对象public TextView tv_name; // 声明行星名称的文本视图对象public TextView tv_desc; // 声明行星描述的文本视图对象}
}在页面代码中创建该适配器示例并交给下拉框设置示例代码如下
// 初始化行星列表的下拉框
private void initPlanetSpinner() {// 获取默认的行星列表即水星、金星、地球、火星、木星、土星planetList Planet.getDefaultList();// 构建一个行星列表的适配器PlanetBaseAdapter adapter new PlanetBaseAdapter(this, planetList);// 从布局文件中获取名叫sp_planet的下拉框Spinner sp_planet findViewById(R.id.sp_planet);sp_planet.setPrompt(请选择行星); // 设置下拉框的标题sp_planet.setAdapter(adapter); // 设置下拉框的列表适配器sp_planet.setSelection(0); // 设置下拉框默认显示第一项// 给下拉框设置选择监听器一旦用户选中某一项就触发监听器的onItemSelected方法sp_planet.setOnItemSelectedListener(new MySelectedListener());
}运行App一开始的下拉框显示默认选择项的图标、标题和内容如下图 点击下拉框页面中央弹出六大行星的列表对话框。可见列表框的各项一齐展示行星的图标、名称以及详细描述。因为对列表布局item_list.xml使用了单独的适配器代码PlanetBaseAdapter所以即使多加几个控件也不怕麻烦。弹出的列表对话框如下图
列表视图ListView
上一小节给下拉框控件设置了基本适配器然而列表效果只在弹出对话框中展示一旦选中某项回到页面时又只显示选中的内容。这么丰富的列表信息没展示在页面上实在是可惜也许用户对好几项内容都感兴趣。若想在页面上直接显示全部列表信息就要引入新的列表视图ListView。列表视图允许在页面上分行展示相似的数据列表例如新闻列表、商品列表、图书列表等方便用户浏览与操作。 ListView同样通过setAdapter方法设置列表项的数据适配器但在操作列表项时它不使用setOnItemSelectedListener方法而是调用setOnItemClickListener方法设置列表项的点击监听器OnItemClickListener有时也调用setOnItemLongClickListener方法设置列表项的长按监听器OnItemLongClickListener。在点击列表项或者长按列表项之时即可触发监听器对应的事件处理方法。除此之外列表视图还增加了几个属性与方法详细说明见下表。
XML中的属性ListView类的设置方法说明dividersetDivider指定分割线的图形。如需取消分割线可将该属性值设为nulldividerHeightsetDividerHeight指定分割线的高度listSelectorsetSelector指定列表项的按压背景状态图形格式
在XML文件中添加ListView很简单只要以下几行代码就声明一个列表视图
ListViewandroid:idid/lv_planetandroid:layout_widthmatch_parentandroid:layout_heightwrap_content /往列表视图中填充数据也很容易先利用基本适配器实现列表适配器再调用setAdapter方法设置适配器对象。下面是使用列表视图在界面上展示行星列表的代码例子
ListPlanet planetList Planet.getDefaultList(); // 获得默认的行星列表
// 构建一个行星列表的列表适配器
PlanetListAdapter adapter new PlanetListAdapter(this, planetList);
// 从布局视图中获取名叫lv_planet的列表视图
lv_planet findViewById(R.id.lv_planet);
lv_planet.setAdapter(adapter); // 设置列表视图的适配器
lv_planet.setOnItemClickListener(adapter); // 设置列表视图的点击监听器
lv_planet.setOnItemLongClickListener(adapter); // 设置列表视图的长按监听器其中列表项的点击事件和长按事件的处理方法代码如下
// 处理列表项的点击事件由接口OnItemClickListener触发
public void onItemClick(AdapterView? parent, View view, int position, long id) {String desc String.format(您点击了第%d个行星它的名字是%s, position 1,mPlanetList.get(position).name);Toast.makeText(mContext, desc, Toast.LENGTH_LONG).show();
}// 处理列表项的长按事件由接口OnItemLongClickListener触发
public boolean onItemLongClick(AdapterView? parent, View view, int position, long id) {String desc String.format(您长按了第%d个行星它的名字是%s, position 1,mPlanetList.get(position).name);Toast.makeText(mContext, desc, Toast.LENGTH_LONG).show();return true;
}运行App打开包含列表视图的测试页面行星列表显示效果如下图
列表视图在各项之间默认展示灰色的分割线点击或长按某项时会显示默认的灰色水波背景。若想修改分割线样式或按压背景则需要调整ListView的对应属性调整的时候注意点说明如下
1.修改列表视图的分割线样式
修改分割线样式要在XML文件中同时设置devider分隔图片与dividerHeight分隔高度两个属性并且遵循给下列两条规则
divider属性设置为null时不能再将dividerHeight属性设置为大于0的数值因为这会导致最后一项没法完全显示底部有一部分被掩盖了。原因是列表高度为wrap_content时系统已按照没有分割线的情况计算列表高度此时dividerHeight占用了n-1块空白分隔区域使得最后一项被挤到背影里面去了。通过代码设置的话务必先调用setDivider方法再调用setDividerHeight方法。如果先调用setDividerHeight再调用setDivider分隔线高度就会变成分隔图片的高度而不是setDividerHeight设置的高度。XML布局文件则不存在divider属性和dividerHeight属性的先后顺序问题。
下面代码示范了如何在代码中正确设置分割线以及如何正确去掉分隔线
if (ck_divider.isChecked()) { // 显示分隔线// 从资源文件获得图形对象Drawable drawable getResources().getDrawable(R.color.red);lv_planet.setDivider(drawable); // 设置列表视图的分隔线lv_planet.setDividerHeight(Utils.dip2px(this, 5)); // 设置列表视图的分隔线高度
} else { // 不显示分隔线lv_planet.setDivider(null); // 设置列表视图的分隔线lv_planet.setDividerHeight(0); // 设置列表视图的分隔线高度
}2.修改列表项的按压背景
若想取消按压列表项之时默认的水波背景可在布局文件中设置也可在代码中设置两种设置方式的注意点说明如下
在布局文件中取消按压背景的话直接将listSelector属性设置为null并不合适因为尽管设为null在按压列表项时然出现橙色背景。只有把listSelector属性设置为透明色才算真正取消背景此时listSelector的属性值如下事先在color.xml中定义好透明色
android:listSelectornull在代码中取消按压背景的话调用setSelector方法不能设置null值因为null值会在运行时报空指针异常。正确的做法是先从资源文件中获得透明色的图形对象再调用setSelector方法设置列表项的按压状态图形设置按压背景的代码如下
// 从资源文件获得图形对象
Drawable drawable getResources().getDrawable(R.color.transparent);
lv_planet.setSelector(drawable); // 设置列表项的按压状态图形列表视图除了以上两处属性修改在实际开发中还有两种用法要特别小心一种是列表视图的高度问题另一种是列表项的点击问题分别叙述如下
1.列表视图的高度问题
在XML文件中如果ListView后面还有其他平级的控件就要将ListView的高度设置为0dp同时权重设置为1确保列表视图扩展到剩余的页面区域如果ListView的高度设置为wrap_content系统就会只给列表视图预留一行高度如此一来只有列表的第一项会显示其他项不显示这显然不是我们所期望的。因此建议列表视图的尺寸参数按照如下方式设置
ListViewandroid:idid/lv_planetandroid:layout_widthmatch_parentandroid:layout_height0dpandroid:layout_weight1/2.列表项的点击问题
通常只要调用setOnItemClickListener方法设置点击监听器点击列表项即可触发列表项的点击事件但如果列表项中存在编辑框或按钮含Button、ImageButton、CheckBox等点击列表项就无法触发点击事件了。缘由在于编辑框和按钮这类控件会抢占焦点因为它们要么等待用户输入、要么等待用户点击按道理用户点击按钮确实应该就触发按钮的点击事件而非触发列表项的点击事件可问题是用户点击列表的其余区域也由于焦点被抢占的缘故导致触发不了列表项的点击事件。 为了规避焦点抢占问题列表视图允许开发者自行设置内部视图的焦点抢占方式该方式在XML文件中由descendantFocusability属性指定在代码中由setDescendantFocusability方法设置详细抢占方式说明见下表。
焦点抢占方式说明代码中的焦点抢占类型XML文件中的焦点抢占属性在子控件之前处理ViewGroup.FOCUS_BEFORE_DESCENDANTSbeforeDescendants在子控件之后处理ViewGroup.FOCUS_AFTER_DESCENDANTSafterDescendants不让子控件处理ViewGroup.FOCUS_BLOCK_DESCENDANTSblockDescendants
注意焦点抢占方式不是由ListView设置而是由列表的根布局设置也就是item_***.xml的根节点。完整的演示代码见源码ListFocusActivity.java、PlanetListWithButtonAdapter.java以及列表项的布局文件item_list_with_button.xml。自行指定焦点抢占方式的界面效果如下图 在上图的界面上选择焦点抢占方式“不让子控件处理FOCUS_BLOCK_DESCENDANTS”之后点击列表项除按钮之外的区域才会弹出列表项点击事件的提示。 接下来实现一个购物车页面将商品列表使用列表视图实现把列表项的相关操作剥离到单独的适配器代码中有利于界面代码的合理解耦。显示效果如下图
网格试图GridView
除了列表视图之外网格视图GridView也是常见的列表类视图它用于分行列显示表格信息比列表视图更适合展示物品清单。除了沿用列表视图的3个方法setAdapter、setOnItemClickListener、setOnItemLongListener网格视图还新增了部分属性与方法新属性与新方法的说明见下表。
XML中的属性代码中的设置方法说明horizontalSpacingsetHorizontalSpacing指定网格项在水平方向的间距verticalSpacingsetVerticalSpacing指定网格项在垂直方向的间距numColumnssetNumColumns指定列的数目stretchModesetStretchMode指定剩余控件的拉伸模式。拉伸模式的取值见下表columnWidthsetColumnWidth指定每列的宽度。拉伸模式为spacingWidth、spacingWidthUniform时必须指定列宽
网格视图拉伸模式的取值说明
XML中的拉伸模式GridView类的拉伸模式说明noneNO_STRETCH不拉伸columnWidthSTRETCH_COLUMN_WIDTH若有剩余空间则拉伸列宽挤掉空隙spacingWidthSTRETCH_SPACING若有剩余空间则列宽不变把空间分配到每列间隙的空隙spacingWidthUniformSTRETCH_SPACING_UNIFORM若有剩余空间则列宽不变把空间分配到每列左右的空隙
在XML文件中添加GridView需要指定列的数目以及空隙的拉升模式示例如下
GridViewandroid:idid/gv_planetandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:numColumns2android:stretchModecolumnWidth /网格视图的按压背景与焦点抢占问题类似于列表视图此外还需要注意网格项的拉伸模式因为同一行的网格项可能占不满该行空间多出来的空间就由拉伸模式决定怎么分配。接下来做个实验看看各种拉伸模式分别呈现什么样的界面效果。实验之前先给网格视图设置青色背景通过观察背景的覆盖区域即可知晓网格项之间的空隙分布。 下面是演示网格视图拉伸模式的代码片段
int dividerPad Utils.dip2px(GridViewActivity.this, 2); // 定义间隔宽度为2dp
gv_planet.setBackgroundColor(Color.CYAN); // 设置背景颜色
gv_planet.setHorizontalSpacing(dividerPad); // 设置列表项在水平方向的间距
gv_planet.setVerticalSpacing(dividerPad); // 设置列表项在垂直方向的间距
gv_planet.setStretchMode(GridView.STRETCH_COLUMN_WIDTH); // 设置拉伸模式
gv_planet.setColumnWidth(Utils.dip2px(GridViewActivity.this, 120)); // 设置每列宽度为120dp
gv_planet.setPadding(0, 0, 0, 0); // 设置网格视图的四周间距
if (arg2 0) { // 不显示分隔线gv_planet.setBackgroundColor(Color.WHITE);gv_planet.setHorizontalSpacing(0);gv_planet.setVerticalSpacing(0);
} else if (arg2 1) { // 不拉伸(NO_STRETCH)gv_planet.setStretchMode(GridView.NO_STRETCH);
} else if (arg2 2) { // 拉伸列宽(COLUMN_WIDTH)gv_planet.setStretchMode(GridView.STRETCH_COLUMN_WIDTH);
} else if (arg2 3) { // 列间空隙(STRETCH_SPACING)gv_planet.setStretchMode(GridView.STRETCH_SPACING);
} else if (arg2 4) { // 左右空隙(SPACING_UNIFORM)gv_planet.setStretchMode(GridView.STRETCH_SPACING_UNIFORM);
} else if (arg2 5) { // 使用padding显示全部分隔线gv_planet.setPadding(dividerPad, dividerPad, dividerPad, dividerPad);
}运行App一开始的行星网格界面如下图 此时没有网格分割线。点击界面顶部的“拉伸模式”下拉框并选择“不拉伸NO_STRETCH”此时每行的网格顶紧挨着多出来的空隙排在当前行的右边如下图 拉伸模式选择“拉伸列宽COLUMN_WIDTH”此时行星网格界面每个网格的宽度都变宽了效果如下图 拉伸模式选择“列间空隙STRETCH_SPACING”此时可见多出来的空隙位于网格项中间如下图 拉伸模式选择“左右空隙SPACING_UNIFORM”此时可见空隙同时出现在网格的左右两边如下图 拉伸模式选择使用padding显示全部分割线此时网格视图的内外边界都显示了分割线如下图 接下来继续在实战中运用网格视图上一节的列表视图已经成功改造了购物车的商品列表现在使用网格视图改造商品频道页面六部手机正好做成三行两列的GridView完整代码参见ShoppingChannelActivity.java。采用网格视图改造的商品频道页面效果如下图
翻页类视图
此节介绍翻页类视图的相关方法包括翻页视图ViewPager如何搭配翻页适配器PageAdapter如何搭配翻页标签栏PagerTabStrip最后结合实战演示如何使用翻页视图实现简单的启动引导页。
翻页视图ViewPager
上一节介绍的列表视图与网络视图一个分行展示另一个分行又分列其实都是在垂直方向上下滑动。有没有一种控件允许页面在水平方向左右滑动就像翻书、翻报纸一样呢为了实现左右滑动的翻页功能Android提供了相应的控件–翻页视图ViewPager。对于ViewPager来说一个页面就是一个项相当于ListView的一个列表项许多个页面组成了ViewPager的页面项。 既然明确了翻页视图的原理类似列表视图和网络视图那它们的用法自然也很类似。例如列表视图和网格视图使用基本适配器BaseAdapter翻页视图则使用适配器PagerAdapter列表视图和网格视图使用列表项的点击监听器OnItemClickListener翻页视图则使用页面变更监听器OnPageChangeListener监听页面切换时间。 下面是翻页视图3个常用方法的说明。
setAdapter设置页面项的适配器。适配器用的是PagerAdapter及其子类。setCurrentItem设置当前页码也就是要显示哪个页面。addOnPageChangeListener添加翻页视图的页面变更监听器。该监听器需要实现接口OnPageChangeListener下的3个方法具体说明如下 onPageScrollStateChanged在页面滑动状态变化时触发。 onPageScrolled在页面滑动过程中触发。 onPageSelected在选中页面时即滑动结束后触发。
在XML文件中添加ViewPager时注意指定完整路径的节点名称示例如下
!-- 注意翻页视图ViewPager的节点名称要填全路径 --
androidx.viewpager.widget.ViewPagerandroid:idid/vp_contentandroid:layout_widthmatch_parentandroid:layout_height370dp /由于翻页视图包含了多个页面项因此要借助翻页适配器展示每个页面。翻页适配器的实现原理与基本适配器类似从PagerAdapter派生的翻页适配器主要实现下面6个方法。
构造方法指定适配器需要处理的数据集合。getCount获取页面的个数。isViewFromObject判断当前视图是否来自指定对象返回viewobject即可。instantiateItem实例化指定位置的页面并将其添加到容器中。destroyItem从容器中销毁指定位置的页面。getPageTitle获得指定页面的标题文本又搭配翻译标签栏时才要实现该方法。 以商品信息为例翻页适配器需要通过构造方法传入商品列表再由instantiateItem方法实例化视图对象并添加至容器详细的翻页适配器代码示例如下
public class ImagePagerAdapater extends PagerAdapter {// 声明一个图像视图列表private ListImageView mViewList new ArrayListImageView();// 声明一个商品信息列表private ListGoodsInfo mGoodsList new ArrayListGoodsInfo();// 图像翻页适配器的构造方法传入上下文与商品信息列表public ImagePagerAdapater(Context context, ListGoodsInfo goodsList) {mGoodsList goodsList;// 给每个商品分配一个专用的图像视图for (int i 0; i mGoodsList.size(); i) {ImageView view new ImageView(context); // 创建一个图像视图对象view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));view.setImageResource(mGoodsList.get(i).pic);mViewList.add(view); // 把该商品的图像视图添加到图像视图列表}}// 获取页面项的个数public int getCount() {return mViewList.size();}// 判断当前视图是否来自指定对象public boolean isViewFromObject(View view, Object object) {return view object;}// 从容器中销毁指定位置的页面public void destroyItem(ViewGroup container, int position, Object object) {container.removeView(mViewList.get(position));}// 实例化指定位置的页面并将其添加到容器中public Object instantiateItem(ViewGroup container, int position) {container.addView(mViewList.get(position));return mViewList.get(position);}
}接着回到活动页面代码给翻页视图设置上述的翻页适配器代码如下
public class ViewPagerActivity extends AppCompatActivity implements OnPageChangeListener {private ListGoodsInfo mGoodsList; // 手机商品列表Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_view_pager);mGoodsList GoodsInfo.getDefaultList();// 构建一个商品图片的翻页适配器ImagePagerAdapater adapter new ImagePagerAdapater(this, mGoodsList);// 从布局视图中获取名叫vp_content的翻页视图ViewPager vp_content findViewById(R.id.vp_content);vp_content.setAdapter(adapter); // 设置翻页视图的适配器vp_content.setCurrentItem(0); // 设置翻页视图显示第一页vp_content.addOnPageChangeListener(this); // 给翻页视图添加页面变更监听器}// 翻页状态改变时触发。state取值说明为0表示静止1表示正在滑动2表示滑动完毕// 在翻页过程中状态值变化依次为正在滑动→滑动完毕→静止public void onPageScrollStateChanged(int state) {}// 在翻页过程中触发。该方法的三个参数取值说明为 第一个参数表示当前页面的序号// 第二个参数表示页面偏移的百分比取值为0到1第三个参数表示页面的偏移距离public void onPageScrolled(int position, float ratio, int offset) {}// 在翻页结束后触发。position表示当前滑到了哪一个页面public void onPageSelected(int position) {Toast.makeText(this, 您翻到的手机品牌是 mGoodsList.get(position).name, Toast.LENGTH_SHORT).show();}
}由于监听器OnPageChangeListener多数情况只用到onPageSelected方法很少用到onPageScrollStateChanged和onPageScrolled两个方法因此Android又提供了简化版的页面变更监听器名为SimpleOnPageChangeListener新的监听器仅需要实现onPageSelected方法。给翻页视图添加简化版监听器的代码示例如下
// 给翻页视图添加简化版的页面变更监听器
vp_content.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {Overridepublic void onPageSelected(int position) {Toast.makeText(ViewPagerActivity.this, 您翻到的手机品牌是 mGoodsList.get(position).name, Toast.LENGTH_SHORT).show();}
});运行App刚开始显示的是第一页向左滑动即可滑动到下一页。第一页显示如下
翻页标签栏PagerTabStrip
尽管翻页视图实现了左右滑动可是没滑动的时候看不出这是个翻页视图而且也不知道当前滑到了哪个页面。为此Android提供了翻页标签栏PagerTabStrip它能够在翻页视图上方显示页面标题从而方便用户的浏览操作。PagerTabStrip类似选项卡效果文本下面有横向点击左右选项卡可切换到对应页面。给放野视图引人入翻页标签栏只需要下列两个步骤
在XML文件的ViewPager节点内部添加PagerTabStrip节点示例如下
!-- 注意翻页视图ViewPager的节点名称要填全路径 --
LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:orientationverticalandroid:padding5dp!-- 注意翻页视图ViewPager的节点名称要填全路径 --androidx.viewpager.widget.ViewPagerandroid:idid/vp_contentandroid:layout_widthmatch_parentandroid:layout_height400dp!-- 注意翻页标签栏PagerTabStrip的节点名称要填全路径 --androidx.viewpager.widget.PagerTabStripandroid:idid/pts_tabandroid:layout_widthwrap_contentandroid:layout_heightwrap_content //androidx.viewpager.widget.ViewPager
/LinearLayout在翻页适配器的代码中重写getPageTitle方法在不同位置返回对应的标题文本示例代码如下
// 获得指定页面的标题文本
public CharSequence getPageTitle(int position) {return mGoodsList.get(position).name;
}完成上述两个步骤之后重新运行App即可观察翻页标签栏的界面效果。这个翻页页面的上方是当前页面的标题左右两边分别是左右页面的标题。如下图 另外若想修改翻页标签栏的文本样式必须在Java代码中调用setTextSize和setTextColor方法才行因为PagerTabStrip不支持在XML文件中设置文本大小和文本颜色只能在代码中设置文本样式具体的设置代码如下
// 初始化翻页标签栏
private void initPagerStrip() {// 从布局视图中获取名叫pts_tab的翻页标签栏PagerTabStrip pts_tab findViewById(R.id.pts_tab);// 设置翻页标签栏的文本大小pts_tab.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);pts_tab.setTextColor(Color.BLACK); // 设置翻页标签栏的文本颜色
}简单的启动引导页
翻页视图的使用范围很广当用户安装一个新应用时首次启动大多出现欢迎页面这个引导页要往右翻好几页才会进入应用主页。这种启动引导页就是通过翻页视图实现的。 下面就来动手打造你的第一个App启动欢迎页吧翻页技术的核心在于页面项的XML布局及其适配器因此首先要设计页面项的布局。一般来说引导页由两部分组成一部分是背景图另一部分时页面下方的一排圆点其中高亮的圆点表示当前位于第几页。启动引导页的界面效果如下。此时为第一页圆点第一个高亮显示。 除了背景图与一排圆点之外最后一页往往还有比个按钮它便是进入应用主页入口。于是页面项的XML文件至少包含3个控件引导页的背景图采用ImageView、底部的一排圆点采用RadioGroup、最后一页的入口按钮采用ButtonXML文件内容如下
RelativeLayout xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_widthmatch_parentandroid:layout_heightmatch_parent!-- 这是引导图片的图像视图 --ImageViewandroid:idid/iv_launchandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:scaleTypefitXY /!-- 这里容纳引导页底部的一排圆点 --RadioGroupandroid:idid/rg_indicateandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_alignParentBottomtrueandroid:layout_centerHorizontaltrueandroid:orientationhorizontalandroid:paddingBottom20dp /!-- 这是最后一页的入口按钮 --Buttonandroid:idid/btn_startandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_centerInParenttrueandroid:text立即开始美好生活android:textColor#ff3300android:textSize22spandroid:visibilitygone /
/RelativeLayout根据上面的XML文件引导页的最后一页如下图只有该页才会显示入口按钮。 写好了页面项的XML布局还得编写启动引导页的适配器代码主要完成3项工作
根据页面项的XML文件构造每页的视图。让当前页码的圆点高亮显示。如果翻到最后一页就显示中间的入口按钮。
启动引导页对应的翻页适配器代码示例如下
public class LaunchSimpleAdapter extends PagerAdapter {private ListView mViewList new ArrayListView(); // 声明一个引导页的视图列表// 引导页适配器的构造方法传入上下文与图片数组public LaunchSimpleAdapter(final Context context, int[] imageArray) {for (int i 0; i imageArray.length; i) {// 根据布局文件item_launch.xml生成视图对象View view LayoutInflater.from(context).inflate(R.layout.item_launch, null);ImageView iv_launch view.findViewById(R.id.iv_launch);RadioGroup rg_indicate view.findViewById(R.id.rg_indicate);Button btn_start view.findViewById(R.id.btn_start);iv_launch.setImageResource(imageArray[i]); // 设置引导页的全屏图片// 每个页面都分配一个对应的单选按钮for (int j 0; j imageArray.length; j) {RadioButton radio new RadioButton(context); // 创建一个单选按钮radio.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));radio.setButtonDrawable(R.drawable.launch_guide); // 设置单选按钮的图标radio.setPadding(10, 10, 10, 10); // 设置单选按钮的四周间距rg_indicate.addView(radio); // 把单选按钮添加到页面底部的单选组}// 当前位置的单选按钮要高亮显示比如第二个引导页就高亮第二个单选按钮((RadioButton) rg_indicate.getChildAt(i)).setChecked(true);// 如果是最后一个引导页则显示入口按钮以便用户点击按钮进入主页if (i imageArray.length - 1) {btn_start.setVisibility(View.VISIBLE);btn_start.setOnClickListener(v - {// 这里要跳到应用主页Toast.makeText(context, 欢迎您开启美好生活,Toast.LENGTH_SHORT).show();});}mViewList.add(view); // 把该图片对应的页面添加到引导页的视图列表}}// 获取页面项的个数public int getCount() {return mViewList.size();}// 判断当前视图是否来自指定对象public boolean isViewFromObject(View view, Object object) {return view object;}// 从容器中销毁指定位置的页面public void destroyItem(ViewGroup container, int position, Object object) {container.removeView(mViewList.get(position));}// 实例化指定位置的页面并将其添加到容器中public Object instantiateItem(ViewGroup container, int position) {container.addView(mViewList.get(position));return mViewList.get(position);}
}碎片Fragment
此节介绍碎片的概念及其用法包括通过静态注册方式使用碎片、通过动态注册方式使用碎片需要配合碎片适配器FragmentPageAdapter并分析两种注册方式的碎片生命周期最后结合实战演示如何使用碎片改进启动引导页。
碎片的静态注册
碎片Fragment是个特别的存在它有点像报纸上的专栏看起来只占据页面的一小块区域但是这一区域有自己的生命周期可以自食其力彷佛独立王国并且该区域只占据空间不扰乱业务添加之后不影响宿主页面的其他区域去除之后也不影响宿主页面的其他区域。 每个碎片都有对应的XML布局文件依据其使用方式可分为静态注册与动态注册两类。静态注册指的是XML文件中直接放置fragment节点类似于一个普通控件可被多个布局文件同时引用。静态注册一般用于某个通用的页面部件如Logo条、广告条等每个活动页面均可直接引用该部件。 下面是碎片页对应的XML文件内容看起来跟列表项与网格项的布局文件差不多。
LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationhorizontalandroid:background#bbffbbTextViewandroid:idid/tv_advandroid:layout_width0dpandroid:layout_heightmatch_parentandroid:layout_weight1android:gravitycenterandroid:text广告图片android:textColor#000000android:textSize17sp /ImageViewandroid:idid/iv_advandroid:layout_width0dpandroid:layout_heightmatch_parentandroid:layout_weight4android:srcdrawable/advandroid:scaleTypefitCenter /
/LinearLayout下面是与上述XML布局对应的碎片代码除了继承自Fragment入口与方法onCreateView两点外其他地方类似活动页面代码。
public class StaticFragment extends Fragment implements View.OnClickListener {protected View mView; // 声明一个视图对象protected Context mContext; // 声明一个上下文对象// 创建碎片视图Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {mContext getActivity(); // 获取活动页面的上下文// 根据布局文件fragment_static.xml生成视图对象mView inflater.inflate(R.layout.fragment_static, container, false);TextView tv_adv mView.findViewById(R.id.tv_adv);ImageView iv_adv mView.findViewById(R.id.iv_adv);tv_adv.setOnClickListener(this); // 设置点击监听器iv_adv.setOnClickListener(this); // 设置点击监听器return mView; // 返回该碎片的视图对象}Overridepublic void onClick(View v) {if (v.getId() R.id.tv_adv) {Toast.makeText(mContext, 您点击了广告文本, Toast.LENGTH_LONG).show();} else if (v.getId() R.id.iv_adv) {Toast.makeText(mContext, 您点击了广告图片, Toast.LENGTH_LONG).show();}}
}若想在活动页面的XML文件中引用上面定义的StaticFragment可以直接添加一个fragment节点但需要注意下列两点
fragment节点必须指定id属性否则App运行会报错。fragment节点必须通过name属性指定碎片类的完整路径。
在布局文件中引用碎片的XML例子如下
LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:orientationvertical!-- 把碎片当作一个控件使用其中android:name指明了碎片来源 --fragmentandroid:idid/fragment_staticandroid:namecom.example.chapter07.fragment.StaticFragmentandroid:layout_widthmatch_parentandroid:layout_height60dp /TextViewandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:gravitycenterandroid:text这里是每个页面的具体内容android:textColor#000000android:textSize17sp /
/LinearLayout运行App此时碎片区域彷佛一个视图其内部控件同样可以接收点击事件。界面显示效果如下图 另外介绍一下碎片在静态注册时的生命周期像活动的基本生命周期方法onCreate、onStart、onResume、onPause、onStop、onDestroy碎片同样也有而且还多出了下面5个生命周期方法。
onAttach与活动页面结合。onCreateView创建碎片视图。onActivityCreated在活动页面创建完毕后调用。onDestroyView回收碎片视图。onDetach与活动页面分离。
至于这些周期方法的先后调用顺序观察日志最简单明了。下面是打开页面时的日志信息此时碎片的onCreate方法先于活动的onCreate方法而碎片的onStart与onResume均在活动的同名方法之后。
10:45:16.418 StaticFragment onAttach
10:45:16.419 StaticFragment onCreate
10:45:16.428 StaticFragment onCreateView
10:45:16.432 FragmentStaticActivity onCreate
10:45:16.436 StaticFragment onActivityCreated
10:45:16.440 StaticFragment onStart
10:45:16.441 FragmentStaticActivity onStart
10:45:16.444 FragmentStaticActivity onResume
10:45:16.446 StaticFragment onResume下面是退出活动页面时的日志信息此时碎片的onPause、onStop、onDestroy都在活动的同名方法之前。
10:52:08.367 StaticFragment onPause
10:52:08.367 FragmentStaticActivity onPause
10:52:09.065 StaticFragment onStop
10:52:09.065 FragmentStaticActivity onStop
10:52:09.067 StaticFragment onDestroyView
10:52:09.072 StaticFragment onDestroy
10:52:09.072 StaticFragment onDetach
10:52:09.072 FragmentStaticActivity onDestroy总结一下在静态注册时除了碎片的创建操作在页面创建之前其他操作没有僭越页面范围。
碎片的动态注册
碎片拥有两种使用方式也就是静态注册和动态注册。相比静态注册在实际开发中动态注册用得更多。静态注册实在XML文件中直接添加fragment节点而动态注册迟至代码执行时才动态添加碎片。动态生成的碎片基本给翻页视图使用要知道ViewPager个Fragment可是一对好搭档。 要想在翻页视图中使用动态碎片关键在于适配器。在“翻页视图ViewPager”节演示翻页功能时用到了翻页适配器PagerAdapter。如果结合使用碎片翻页视图的适配器就要改用碎片适配器FragmentPagerAdapter。与翻页适配器相比碎片适配器增加了getItem方法用于获取指定位置的碎片同时去掉了isViewFromObject、instantiateItem、destroyItem三个方法用起来更加容易。下面是一个碎片适配器的实现代码例子。
public class MobilePagerAdapter extends FragmentPagerAdapter {private ListGoodsInfo mGoodsList new ArrayListGoodsInfo(); // 声明一个商品列表// 碎片页适配器的构造方法传入碎片管理器与商品信息列表public MobilePagerAdapter(FragmentManager fm, ListGoodsInfo goodsList) {super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);mGoodsList goodsList;}// 获取碎片Fragment的个数public int getCount() {return mGoodsList.size();}// 获取指定位置的碎片Fragmentpublic Fragment getItem(int position) {return DynamicFragment.newInstance(position,mGoodsList.get(position).pic, mGoodsList.get(position).desc);}// 获得指定碎片页的标题文本public CharSequence getPageTitle(int position) {return mGoodsList.get(position).name;}
}上面的适配器代码在getItem方法中不调用碎片的构造方法却调用了newInstance方法目的是给碎片对象传递参数信息。由newInstance方法内部先调用构造方法创建碎片对象再调用setArguments方法塞进请求参数然后在onCreateView中调用getArgument方法才能取出请求参数。下面是在动态注册时传递请求参数的碎片代码例子
public class DynamicFragment extends Fragment {protected View mView; // 声明一个视图对象protected Context mContext; // 声明一个上下文对象private int mPosition; // 位置序号private int mImageId; // 图片的资源编号private String mDesc; // 商品的文字描述// 获取该碎片的一个实例public static DynamicFragment newInstance(int position, int image_id, String desc) {DynamicFragment fragment new DynamicFragment(); // 创建该碎片的一个实例Bundle bundle new Bundle(); // 创建一个新包裹bundle.putInt(position, position); // 往包裹存入位置序号bundle.putInt(image_id, image_id); // 往包裹存入图片的资源编号bundle.putString(desc, desc); // 往包裹存入商品的文字描述fragment.setArguments(bundle); // 把包裹塞给碎片return fragment; // 返回碎片实例}// 创建碎片视图public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {mContext getActivity(); // 获取活动页面的上下文if (getArguments() ! null) { // 如果碎片携带有包裹就打开包裹获取参数信息mPosition getArguments().getInt(position, 0); // 从包裹取出位置序号mImageId getArguments().getInt(image_id, 0); // 从包裹取出图片的资源编号mDesc getArguments().getString(desc); // 从包裹取出商品的文字描述}// 根据布局文件fragment_dynamic.xml生成视图对象mView inflater.inflate(R.layout.fragment_dynamic, container, false);ImageView iv_pic mView.findViewById(R.id.iv_pic);TextView tv_desc mView.findViewById(R.id.tv_desc);iv_pic.setImageResource(mImageId);tv_desc.setText(mDesc);return mView; // 返回该碎片的视图对象}
}现在有了适用于动态注册的适配器与碎片对象还需要一个活动页面展示翻页视图及其搭配的碎片适配器。下面是动态注册用到的活动页面代码。
public class FragmentDynamicActivity extends AppCompatActivity {Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_fragment_dynamic);ListGoodsInfo goodsList GoodsInfo.getDefaultList();// 构建一个手机商品的碎片翻页适配器MobilePagerAdapter adapter new MobilePagerAdapter(getSupportFragmentManager(), goodsList);// 从布局视图中获取名叫vp_content的翻页视图ViewPager vp_content findViewById(R.id.vp_content);vp_content.setAdapter(adapter); // 设置翻页视图的适配器vp_content.setCurrentItem(0); // 设置翻页视图显示第一页}
}运行App初始的碎片界面默认显示第一页。向左滑切换下一页一直滑动即可滑到最后一页界面总体效果类似“翻页标签栏PagerTabStrip”那样说明修改成功。界面显示如下 接下来观察动态注册时的碎片生命周期。按惯例分别在活动代码与碎片内部补充生命周期的日志然后观察App运行日志。下面是打开活动页面时的日志信息
14:55:35.652 FragmentDynamicActivity onCreate
14:55:35.655 FragmentDynamicActivity onStart
14:55:35.656 FragmentDynamicActivity onResume
14:55:35.679 DynamicFragment onAttach position0
14:55:35.680 DynamicFragment onCreate position0
14:55:35.686 DynamicFragment onCreateView position0
14:55:35.687 DynamicFragment onActivityCreated position0
14:55:35.687 DynamicFragment onStart position0
14:55:35.688 DynamicFragment onAttach position0
14:55:35.689 DynamicFragment onCreate position0
14:55:35.695 DynamicFragment onCreateView position1
14:55:35.697 DynamicFragment onActivityCreated position1
14:55:35.698 DynamicFragment onStart position1
14:55:35.701 DynamicFragment onResume position0下面是退出活动页面时的日志信息
14:59:45.660 DynamicFragment onPause position0
14:59:45.660 FragmentDynamicActivity onPause
14:59:46.382 DynamicFragment onStop position0
14:59:46.382 DynamicFragment onStop position1
14:59:46.382 FragmentDynamicActivity onStop
14:59:46.384 DynamicFragment onDestroyView position0
14:59:46.385 DynamicFragment onDestroy position0
14:59:46.385 DynamicFragment onDetach position0
14:59:46.386 DynamicFragment onDestroyView position1
14:59:46.386 DynamicFragment onDestroy position1
14:59:46.387 DynamicFragment onDetach position1
14:59:46.387 FragmentDynamicActivity onDestroy日志收集完毕分析其中的奥秘总结一下主要有以下3点
动态注册时碎片的onCreate方法在活动的onCreate之后其余方法的先后顺序与静态注册时保持一致。注意onCreateView方法无论是静态注册还是动态注册该方法都在活动的onCreate方法之后可见该方法的确在页面创建之后才调用。最重要一点进入第一个碎片之际实际只加载了第一页和第二页并没有加载所有碎片页这正是碎片动态注册的优点。无论当前位于哪一页系统都只会加载当前页及相邻的左右两页总共加载不超过3页。一旦发生页面切换相邻页面就被加载非相邻页面就被回收。这么做的好处是节省了宝贵的系统资源只有用户正在浏览与将要浏览的碎片才会被加载避免所有碎片页一起加载造成资源浪费而这正式普通翻页视图的缺点。
改进的启动引导页
接下来将碎片用于实战对“简单的启动引导页”加以改进。与之前相比XML文件不变改动的都是Java代码。用于启动引导页的碎片适配器代码如下
public class LaunchImproveAdapter extends FragmentPagerAdapter {private int[] mImageArray; // 声明一个图片数组// 碎片页适配器的构造方法传入碎片管理器与图片数组public LaunchImproveAdapter(FragmentManager fm, int[] imageArray) {super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);mImageArray imageArray;}// 获取碎片Fragment的个数public int getCount() {return mImageArray.length;}// 获取指定位置的碎片Fragmentpublic Fragment getItem(int position) {return LaunchFragment.newInstance(position, mImageArray[position]);}
}以上的碎片适配器代码到时简单原来与视图控件有关的操作都挪到碎片代码当中了下面是每个启动页的碎片代码例子
public class LaunchFragment extends Fragment {protected View mView; // 声明一个视图对象protected Context mContext; // 声明一个上下文对象private int mPosition; // 位置序号private int mImageId; // 图片的资源编号private int mCount 4; // 引导页的数量// 获取该碎片的一个实例public static LaunchFragment newInstance(int position, int image_id) {LaunchFragment fragment new LaunchFragment(); // 创建该碎片的一个实例Bundle bundle new Bundle(); // 创建一个新包裹bundle.putInt(position, position); // 往包裹存入位置序号bundle.putInt(image_id, image_id); // 往包裹存入图片的资源编号fragment.setArguments(bundle); // 把包裹塞给碎片return fragment; // 返回碎片实例}// 创建碎片视图public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {mContext getActivity(); // 获取活动页面的上下文if (getArguments() ! null) { // 如果碎片携带有包裹就打开包裹获取参数信息mPosition getArguments().getInt(position, 0); // 从包裹获取位置序号mImageId getArguments().getInt(image_id, 0); // 从包裹获取图片的资源编号}// 根据布局文件item_launch.xml生成视图对象mView inflater.inflate(R.layout.item_launch, container, false);ImageView iv_launch mView.findViewById(R.id.iv_launch);RadioGroup rg_indicate mView.findViewById(R.id.rg_indicate);Button btn_start mView.findViewById(R.id.btn_start);iv_launch.setImageResource(mImageId); // 设置引导页的全屏图片// 每个页面都分配一个对应的单选按钮for (int j 0; j mCount; j) {RadioButton radio new RadioButton(mContext); // 创建一个单选按钮radio.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));radio.setButtonDrawable(R.drawable.launch_guide); // 设置单选按钮的图标radio.setPadding(10, 10, 10, 10); // 设置单选按钮的四周间距rg_indicate.addView(radio); // 把单选按钮添加到页面底部的单选组}// 当前位置的单选按钮要高亮显示比如第二个引导页就高亮第二个单选按钮((RadioButton) rg_indicate.getChildAt(mPosition)).setChecked(true);// 如果是最后一个引导页则显示入口按钮以便用户点击按钮进入首页if (mPosition mCount - 1) {btn_start.setVisibility(View.VISIBLE);btn_start.setOnClickListener(v - {// 这里要跳到应用主页Toast.makeText(mContext, 欢迎您开启美好生活, Toast.LENGTH_SHORT).show();});}return mView; // 返回该碎片的视图对象}
}经过碎片改造后的启动引导页其界面效果跟“简单的启动引导页”是一样的。尽管看不出界面上的差异但碎片之后至少有以下两个好处。
加快启动速度因为动态注册的碎片一开始只会加载前两个启动页对比原来加载所有启动页至少4项,无疑大幅减少了加载页的数量从而提升启动速度。降低代码耦合把视图操作剥离到单独的碎片代码不与适配器代码混合在一起方便后继的代码维护工作。
工程源码
全文对应的工程源码可点击源码链接下载。