转载请指明出处 WangYouHu 版权归博主所有
Fragment应用越来越广泛 当初在android3.0加入fragment是为了解决手机和平板之间的屏幕兼容问题 平板屏幕比较大
屏幕空间不能充分利用的解决方案 现在手机上很多app也使用到了fragment解决屏幕小的问题 如QQ 微信 爱奇艺 网易云音乐等
思路是通过你点击一个tab 然后显示到相应的界面 对一些fragment进行hide和show方法 在为空的时候进行了判断则创建一个新的对象进行显示 避免remove再次重建 数据丢失和多次重建的弊端
目前很多公司要求谷歌放弃fragment 因为他生命周期很复杂 还会出现很多漏洞 但现在很多公司要求会使用fragment
我们遇到的问题是什么?
- 1由于fragment和activity生命周期不同 可能会出现内存不足销毁对象和屏幕旋转生命周期重建 造成重叠
- 2当对fragment不进行正确添加删除隐藏显示会出现重叠不显示找不到对象
- 3对象存在 多次重建 影响性能
- 4存在父类fragment和子类fragment多次嵌套滑动冲突的现象
下面来看一下fragment和activity的生命周期?
在create和destroy时明显不同 fragment是嵌套在activity中 生命周期戚戚相关 有关详细资料请看谷歌android developers官网
重叠现象?
重叠现象出现可能是你的不正确操作和内存回收造成生命周期重建等 这必须解决 否则会造成用户的体验甚至卸载你的应用 当你多次重建或多次显示就会出现重叠现象
不显示现象?
当你没有初始化对象 对象没有添加进去或对象被隐藏 不能在正确的时候隐藏和显示
下面来看一张重叠的图 原因是内存不足系统自动释放内存造成或屏幕旋转造成生命周期改变
我在示例中使用了自定义tab+framLayout和官方tab+viewPager和PagerTabStrip+viewpager实现 你完全可以使用我的示例来做你的项目基本框架
首先来看我的底部tab实现方法 作为父类fragment
在这个布局中我实现点击布局切换framgment 为了用户点击更精确 在里面放了三个布局用来放tab图标和文字 也是为了更精确tab图标和文字的位置 细心的你肯定看到了这三个布局加入了id 一般布局是不用设置id的 通过点击布局选中tab 避免了设置图标为切换fragment有时候点不住的问题 更大的范围追求更高的体验
为什么不使用viewPager而使用framLayout替代楠?
这是用作父类fragment 如果实现滑动 就会和子类fragment进行滑动冲突 所以我设计父类framgent作为不可滑动 当然viewPager也可以自定义不滑动 但又framLayout这个好东西 谁还多此一举进行自定义楠
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/content">
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<RelativeLayout
android:layout_weight="1"
android:id="@+id/message_layout"
android:layout_height="match_parent"
android:layout_width="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical">
<ImageView
android:id="@+id/image_message"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
app:srcCompat="@android:color/holo_purple" />
<TextView
android:id="@+id/message_text"
android:layout_gravity="center_horizontal"
android:text="消息"
android:textColor="#82858b"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:layout_weight="1"
android:id="@+id/news_layout"
android:layout_height="match_parent"
android:layout_width="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical">
<ImageView
android:id="@+id/image_news"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
app:srcCompat="@android:color/holo_blue_light" />
<TextView
android:id="@+id/news_text"
android:layout_gravity="center_horizontal"
android:text="动态"
android:textColor="#82858b"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:layout_weight="1"
android:id="@+id/setting_layout"
android:layout_height="match_parent"
android:layout_width="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical">
<ImageView
android:id="@+id/image_setting"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
app:srcCompat="@android:color/holo_red_light" />
<TextView
android:id="@+id/setting_text"
android:layout_gravity="center_horizontal"
android:text="设置"
android:textColor="#82858b"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
在mainactivity中继承点击 然后声明控件
继承点击是为了省略代码 更主要是可以统一规划 避免了多次重写无用代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private MessageFragment messageFragment;
private NewsFragment newsFragment;
private SettingFragment settingFragment;
private View messageLayout;
private View newsLayout;
private View settingLayout;
private ImageView message_image;
private ImageView news_image;
private ImageView setting_image;
private TextView message_text;
private TextView news_text;
private TextView setting_text;
private FragmentManager fragmentManager;
来看我的onctreate方法 设置无顶部toolbar initViews是我用来初始化的方法 fragmentManager是我们的碎片管理器
getFragmentManager ();是以前旧的方法 现在使用v4包中的getSupportFragmentManager ();来兼容 如果使用旧的方法可能会出现问题
在打开这个软件默认进入选中的第一项 在这里面进行相应的操作
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate ( savedInstanceState );
// 设置无toolbar
requestWindowFeature ( Window.FEATURE_NO_TITLE );
setContentView ( R.layout.activity_main );
initViews ();
fragmentManager = getSupportFragmentManager ();
//设置选中
setTabSelection(0);
}
private void initViews() {
messageLayout = findViewById ( R.id.message_layout );
newsLayout = findViewById ( R.id.news_layout );
settingLayout = findViewById ( R.id.setting_layout );
message_image = findViewById ( R.id.image_message );
news_image = findViewById ( R.id.image_news );
setting_image = findViewById ( R.id.image_setting );
message_text = findViewById ( R.id.message_text );
news_text = findViewById ( R.id.news_text );
setting_text = findViewById ( R.id.setting_text );
messageLayout.setOnClickListener ( this );
newsLayout.setOnClickListener ( this );
settingLayout.setOnClickListener ( this );
}
当类扩展点击的方法需要实现以下方法 这里是每个tab布局的点击实现的功能 setTabSelection是我写的一个方法 当点击底部tab选中实现的功能 也增加了代码的规范性
@Override
public void onClick(View v) {
switch (v.getId ()) {
case R.id.message_layout:
setTabSelection(0);
break;
case R.id.news_layout:
setTabSelection(1);
break;
case R.id.setting_layout:
setTabSelection(2);
break;
default:
break;
}
}
FragmentTransaction 是framgent操作的分管理者 专门用来管理fragment一些操作
当点击的时候清除所有的项(实现不选中的tab图标 背景颜色 字体颜色等)和 隐藏所有项 (避免出现重叠)
当判断是哪一项 则设置选中tab的样式 判断碎片是否为空 当为空初始化添加 若不为空则显示 (这避免了多次重建出现重叠)记住千万别remove 新建一个需要时间的 为了用户的更高体验 也会出现空对象的问题 还有一个核心问题- 移除了 你的用户缓存信息则没有了
当然最后别忘了提交commit();
设施选中状态样式 也可以加入动画增加自己的美观 我觉得帧动画就是一个不错的选择
private void setTabSelection(int index) {
//清除选中
clearSelection();
FragmentTransaction transaction=fragmentManager.beginTransaction ();
//隐藏碎片
hideFragment(transaction);
switch (index){
case 0:
message_image.setImageResource ( R.drawable.ic_launcher_background );
message_text.setTextColor ( Color.parseColor ( "#82858b" ) );
//判断碎片是否为空 以免重复建立 影响性能
if (messageFragment==null){
messageFragment=new MessageFragment ();
transaction.add ( R.id.content,messageFragment );
}else {
transaction.show ( messageFragment );
}
break;
case 1:
news_image.setImageResource ( R.drawable.ic_launcher_background );
news_text.setTextColor ( Color.parseColor ( "#82858b" ) );
//判断碎片是否为空 以免重复建立 影响性能
if (newsFragment==null){
newsFragment=new NewsFragment ();
transaction.add ( R.id.content,newsFragment );
}else {
transaction.show ( newsFragment );
}
break;
case 2:
setting_image.setImageResource ( R.drawable.ic_launcher_background );
setting_text.setTextColor ( Color.parseColor ( "#82858b" ) );
//判断碎片是否为空 以免重复建立 影响性能
if (settingFragment==null){
settingFragment=new SettingFragment ();
transaction.add ( R.id.content,settingFragment );
}else {
transaction.show ( settingFragment );
}
break;
}
transaction.commit ();
}
设置这三个tab不选中的原生样式 当然你可以根据自己需要而定 把图片放到drawable中 找到合适你的颜色和图片 注意图片为了兼容不同大的屏幕 建议放不同尺寸的图片在drawable中 你也可以在drawable自定义简单图片用来作为你的背景 一般有椭圆 矩形 磨角矩形 还可以写边框 他会根据手机屏幕自动适应 是一个不错的工具
private void clearSelection() {
//设置清除后的图片文字修改
message_image.setImageResource ( R.drawable.ic_launcher_background );
message_text.setTextColor ( Color.parseColor ( "#82858b" ) );
news_image.setImageResource ( R.drawable.ic_launcher_background );
news_text.setTextColor ( Color.parseColor ( "#82858b" ) );
setting_image.setImageResource ( R.drawable.ic_launcher_background );
setting_text.setTextColor ( Color.parseColor ( "#82858b" ) );
}
隐藏碎片也是核心了 避免重叠 需要判断是否为空 重建否则会崩溃
为什么不选择删除楠?
删除重建影响性能 在创建的时候判断是否为空再创建 这也是对资源的正确分配
private void hideFragment(FragmentTransaction transaction) {
//隐藏碎片 避免重叠
if (messageFragment!=null){
transaction.hide ( messageFragment );
}if (newsFragment!=null){
transaction.hide ( newsFragment );
}if (settingFragment!=null){
transaction.hide ( settingFragment );
}
}
内存过大系统回收内存也会造成重叠 他再次打开会从缓存加载已有对象 直接导致多次创建
这里才是核心 在缓存的时候正确处理 让他不保存或隐藏 这样才可以避免重叠 在恢复的时候选中第一个 当然我是怕隐藏了全部 做出的处理 二次处理有利于性能的稳定 减少bug
@Override
protected void onSaveInstanceState(Bundle outState) {
FragmentTransaction transaction=fragmentManager.beginTransaction ();
hideFragment(transaction);
transaction.commit ();
super.onSaveInstanceState ( outState );
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
FragmentTransaction transaction=fragmentManager.beginTransaction ();
hideFragment(transaction);
transaction.commit ();
setTabSelection(0);
super.onRestoreInstanceState ( savedInstanceState );
}
到这里父类-自定义tab+framLayout就实现完毕
但又出现一个问题 当进入后台恢复前台以及系统回收内存则会隐藏所有 虽然解决了重叠 但不显示也是气人的bug 不少人反映 我又进行了修改
对前台进入后台后再次恢复前台出现所有不显示的bug进行了处理
先来看原来的代码
@Override
protected void onSaveInstanceState(Bundle outState) {
if (outState!=null){
FragmentTransaction transaction=fragmentManager.beginTransaction ();
if (messageFragment!=null){
transaction.hide ( messageFragment );
}if (newsFragment!=null){
transaction.hide ( newsFragment );
}if (settingFragment!=null){
transaction.hide ( settingFragment );
}
transaction.commit ();
}
super.onSaveInstanceState ( outState );
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
FragmentTransaction transaction=fragmentManager.beginTransaction ();
if (savedInstanceState!=null){
//隐藏碎片 避免重叠
if (messageFragment!=null){
transaction.hide ( messageFragment );
}if (newsFragment!=null){
transaction.hide ( newsFragment );
}if (settingFragment!=null) {
transaction.hide ( settingFragment );
}
transaction.commit ();
}
super.onRestoreInstanceState ( savedInstanceState );
}
在进行数据恢复的时候选择了隐藏所有fragment 所以活动销毁或进入后台都会再次启用数据恢复 在这些缓存机制我都是进行了全部隐藏 否则还是会重叠 主要是要研究生命周期 从根本找出问题
我选择在activity生命周期进行显示的周期进行了一个判断 如果缓存不为空则进行选中
下面来看我修改的东西
添加一个全局对象 用来判断数据缓存是否为空 因为第一次显示你如果不判断直接造成重叠
private Bundle bundle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate ( savedInstanceState );
this.bundle=savedInstanceState;
}
你可以根据需要选择恢复的tab项 这里我选择恢复第一项 可以写一个string 当显示放入isHaden 当选中哪一项放到缓存中 判断是哪一项保存住了 然后进行恢复
@Override
protected void onResume() {
super.onResume ();
if (bundle!=null){
setTabSelection ( 0 );
}
}
这里重写保存数据和恢复数据 选中放到了activity显示的地方 这样可以在不同的生命周期进行恢复 解决了无法提交两次的问题 在数据恢复的时候我添加了恢复fragment1
但不显示 因为前面已经把所有的framgent进行了隐藏进行了提交 再次提交已经解决不了问题了 他而且在这里面完成的任务也是隐藏所有的fragment
@Override
protected void onSaveInstanceState(Bundle outState) {
if (outState!=null){
FragmentTransaction transaction=fragmentManager.beginTransaction ();
if (messageFragment!=null){
transaction.hide ( messageFragment );
}if (newsFragment!=null){
transaction.hide ( newsFragment );
}if (settingFragment!=null){
transaction.hide ( settingFragment );
}
transaction.commit ();
}
super.onSaveInstanceState ( outState );
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
FragmentTransaction transaction=fragmentManager.beginTransaction ();
if (savedInstanceState!=null){
//隐藏碎片 避免重叠
if (messageFragment!=null){
transaction.hide ( messageFragment );
}if (newsFragment!=null){
transaction.hide ( newsFragment );
}if (settingFragment!=null) {
transaction.hide ( settingFragment );
}
transaction.commit ();
}
super.onRestoreInstanceState ( savedInstanceState );
}
下面来看子类第一种官方tab+viewpager实现
tablayout中的项在xml中可以不写 在类中写 如下方法
实现文字加图标
tabLayout.getTabAt ( 0 ).setText ( “我的” ).setIcon ( R.drawable.ic_launcher_background );
实现文字
tabLayout.getTabAt ( 0 ).setText ( “我的” );
xml布局则使用text和icon属性 你可以在这里自定义大小一些属性 更容易看到效果 在类中写就有点麻烦了 毕竟就几个tab
下面是一个viewPager 它可以实现滑动 这样就完美的解脱父类和子类fragment滑动冲突了
我看了一下爱奇艺和in这两个软件就没有考虑这个问题 有时候体验真的糟糕
<?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="match_parent"
android:orientation="vertical">
<android.support.design.widget.TabLayout
android:id="@+id/tabMode"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:icon="@color/colorPrimary"
android:text="Left" />
<android.support.design.widget.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:icon="@color/colorAccent"
android:text="Center" />
<android.support.design.widget.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:icon="@color/colorPrimaryDark"
android:text="Right" />
</android.support.design.widget.TabLayout>
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
这是我写的辅助类工具 是viewPager的适配器
里面加入一个传递参数的方法 传入的第几项
因为是在viewPager中的如果为空则创建一个新对象 他是放在屏幕外的 所以只需要考虑为空的时候 不为空则直接滑动得到了 一般这个子类fragment再嵌套fragment没见过里面子类重叠 因为是viewPager里面的东西在屏幕外 则不会重叠
这个适配器挺简单的 谷歌对fragmentPagerAdapter的优化让我们不要再自定义那么多方法
当你要添加你自己的类写入自己的类名
public class MyAdapterM extends FragmentPagerAdapter {
private int num;
Wang1 wang1;
Wang2 wang2;
Wang3 wang3;
public MyAdapterM(FragmentManager fm, int num) {
super ( fm );
this.num=num;
}
@Override
public Fragment getItem(int i) {
switch (i){
case 0:
if (wang1==null){
return new Wang1 ();
}
case 1:
if (wang2==null){
return new Wang2 ();
}
case 2:
if (wang3==null){
return new Wang3 ();
}
default:
return null;
}
}
@Override
public int getCount() {
return num;
}
}
这是我的类文件 我是采用了view的方法 父类fragment则采用add的方法
public class MessageFragment extends Fragment {
private ViewPager viewPager;
private TabLayout tabLayout;
// private List<Fragment> fragmentList;
private MyAdapterM myAdapterM;
private View[] views;
注意fragment中的控件一定要在view中查找 使用getActivity方法可能会出现空对象错误 如果需要也可以设置一个全局对象
但我更倾向于view查找对象 view指明了布局的id 给与了确定的位置就不会报错了 记住一定return的是view 否则对象错误
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater , @Nullable ViewGroup container , @Nullable Bundle savedInstanceState) {
View view = inflater.inflate ( R.layout.message_layout , container , false );
viewPager = view.findViewById ( R.id.viewPager );
tabLayout = view.findViewById ( R.id.tabMode );
setUI ();
return view;
}
指明view具有多少对象 并一一添加进去 注意0则是1
然后初始化MyAdapterM类 并传入参数
并一一相互关联
onTabSelected方法中写入选中tab图标和text的样式 当然你可以根据需要
在这里我考虑了安卓低版本有些属性不可以设置 你根据需要设置
private void setUI() {
views = new View[ 3 ];
LayoutInflater layoutInflater = LayoutInflater.from ( getActivity () );
views[ 0 ] = layoutInflater.inflate ( R.layout.wang1 , null );
views[ 1 ] = layoutInflater.inflate ( R.layout.wang2 , null );
views[ 2 ] = layoutInflater.inflate ( R.layout.wang3 , null );
// fragmentList = new ArrayList<> ();
// fragmentList.add ( new Wang1 () );
// fragmentList.add ( new Wang2 () );
// fragmentList.add ( new Wang3 () );
// tabLayout.getTabAt ( 0 ).setText ( "我的" ).setIcon ( R.drawable.ic_launcher_background );
// tabLayout.getTabAt ( 0 ).setText ( titles[0] ).setIcon ( R.drawable.ic_launcher_background );
// tabLayout.getTabAt ( 1 ).setText ( titles[1] );
// tabLayout.getTabAt ( 2 ).setText ( titles[2] );
myAdapterM = new MyAdapterM (getChildFragmentManager (),tabLayout.getTabCount ());
viewPager.setAdapter ( myAdapterM );
// tabLayout.setupWithViewPager ( viewPager );
viewPager.addOnPageChangeListener ( new TabLayout.TabLayoutOnPageChangeListener ( tabLayout ) );
tabLayout.addOnTabSelectedListener ( new TabLayout.OnTabSelectedListener () {
@Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem ( tab.getPosition () );
if (tab.getPosition () == 1) {
//这里是一个xml写项 tablayout改为相应的布局
tabLayout.setBackgroundColor ( ContextCompat.getColor ( getActivity () , R.color.colorAccent ) );
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
} );
}
到这里就写完了第一个子类示例 下面是效果图 它可以实现点击切换以及滑动切换 有点像网易云顶部的界面 根据自己需要设计样式
下面来看使用PagerTabStrip+viewpager实现嵌套 PagerTabStrip是一个很老的控件 而且不是很常用 但在爱奇艺 慕课网 腾讯视频具有很多选项的时候就能用到了 解决了屏幕放不开太多控件 又想解决的方案
<?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="match_parent"
android:orientation="vertical">
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.v4.view.PagerTabStrip
android:id="@+id/pagerTabStrip"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="top"
/>
<!--顶部top 底部bottom-->
</android.support.v4.view.ViewPager>
</LinearLayout>
同样是封装在view中 并定义适配器属性
public class NewsFragment extends Fragment {
private ViewPager viewPager;
private View[] views;
private String[] topstring={"电影","音乐","直播"};
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater , @Nullable ViewGroup container , @Nullable Bundle savedInstanceState) {
View view=inflater.inflate ( R.layout.news_layout,container,false );
viewPager=view.findViewById ( R.id.viewPager );
LayoutInflater layoutInflater=LayoutInflater.from ( getActivity () );
views=new View[3];
views[0]=layoutInflater.inflate ( R.layout.my1,null );
views[1]=layoutInflater.inflate ( R.layout.my2,null );
views[2]=layoutInflater.inflate ( R.layout.my3,null );
viewPager.setAdapter ( new MyAdapter() );
return view;
}
在这里对对象进行了销毁 当加载信息过大的时候进行销毁也是对内存性能的一个优化
你也可以选择不销毁 viewPager是不会发生重叠 而且对对象进行了保存 你只需要把此重写方法删除即可 或则选择默认重写方法
private class MyAdapter extends PagerAdapter {
/**
* 得到数量
* @return
*/
@Override
public int getCount() {
return views.length;
}
/**
* 来源
* @param
* @param o
* @return
*/
@Override
public boolean isViewFromObject(@NonNull View p , @NonNull Object o) {
return p==o;
}
/**
* 点击处理
* @param container
* @param position
* @return
*/
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container , int position) {
container.addView ( views[position] );
return views[position];
}
/**
* 设置标题
* @param position
* @return
*/
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return topstring[position];
}
/**
* 销毁项
* @param container
* @param position
* @param object
*/
@Override
public void destroyItem(@NonNull ViewGroup container , int position , @NonNull Object object) {
container.removeView ( views[position] );
}
这时第二个子类嵌套完成 下面来看效果图 当然根据需要自己调整样式 谷歌安卓官网有更多的参数
你也可以点进去源码查看理解 其实看源码是进步的快速渠道 但如果你是小白 我建议你不要看 先看安卓官网
下面来写第三个嵌套子类 更倾向于淘宝美团的fragment 这里使用了滚动布局加textView实现点击切换 同样是不使用viewPager 以免造成滚动布局相互冲突 framlayout也可以嵌套一个scrollview 用来解决画面屏幕资源不够的问题
在xml布局中可以看到我使用了scrollview滚动布局 并用framLayout写入fragment 我在使用垂直布局的时候选择了设置权重 这更会优化你的屏幕显示比例 而不是指定大小 对ui界面优化
<?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="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:layout_weight="2">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/tvLayout1"
android:layout_width="match_parent"
android:layout_height="350dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="20sp"
android:gravity="center"
android:text="中国" />
</LinearLayout>
<LinearLayout
android:id="@+id/tvLayout2"
android:layout_width="match_parent"
android:layout_height="350dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="20sp"
android:gravity="center"
android:text="美国" />
</LinearLayout>
<LinearLayout
android:id="@+id/tvLayout3"
android:layout_width="match_parent"
android:layout_height="350dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="20sp"
android:gravity="center"
android:text="加拿大" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<FrameLayout
android:id="@+id/scrollIndicatorDown"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</LinearLayout>
声明和初始化控件
public class SettingFragment extends Fragment {
private FrameLayout frameLayout;
private TextFragment1 textFragment1;
private TextFragment2 textFragment2;
private TextFragment3 textFragment3;
private FragmentManager fragmentManager;
private TextView textView1, textView2, textView3;
private View tvLayout1,tvLayout2,tvLayout3;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater , @Nullable ViewGroup container , @Nullable Bundle savedInstanceState) {
View view = inflater.inflate ( R.layout.setting_layout , container , false );
textView1 = view.findViewById ( R.id.tv1 );
textView2 = view.findViewById ( R.id.tv2 );
textView3 = view.findViewById ( R.id.tv3 );
tvLayout1=view.findViewById ( R.id.tvLayout1);
tvLayout2=view.findViewById ( R.id.tvLayout2);
tvLayout3=view.findViewById ( R.id.tvLayout3);
frameLayout=view.findViewById ( R.id.scrollIndicatorDown );
// setUI();
setTabSelection ( 0 );
// setTabSelection ( 0 );
return view;
}
这里是上面注释掉的代码 我希望减轻点负担给系统 放在创建完成未显示之前 让他们分工明确 你也可以省略这步
切记 不要在这里使用getActivity查找对象id绑定 可能出现空对象
在这里实现控件的功能 因为在onCreateView生命周期的时候可能并没有初始化好id
代码实现是点击的功能实现 和选中第一个项并显示
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated ( savedInstanceState );
setUI ();
setTabSelection ( 0 );
}
在这里我遇到了重建不显示fragment的问题 很是纠结 在fragment判断存在的时候加了log打印 可以看出fragment一直存在 但就是不显示 这也是大家很容易忽视的一部分
最后通过修改getFragmentManager ();为getChildFragmentManager ();
getChildFragmentManager ();是子类碎片管理器 用来嵌套用的
getFragmentManager ();是父类碎片管理器 本来觉得没什么大不了的 但出现了bug 然后就显示了
安卓是一个分工规范的系统 他在封装方法并未写可以管理父类和子类的方法
也讲解一下getFragmentManager ();已弃用 建议使用getSupportFragmentManager ();
安卓库的更新 调用最新的方法会有简洁稳定全面的体验
private void setTabSelection(int index) {
//注意在父类fragment中写子类fragment需要使用特殊的碎片管理器 getChildFragmentManager
fragmentManager=getChildFragmentManager ();
//清除选中
clearSelection();
FragmentTransaction transaction=fragmentManager.beginTransaction ();
//隐藏碎片
hideFragment2(transaction);
switch (index){
case 0:
textView1.setTextColor ( Color.parseColor ( "#00574B" ) );
tvLayout1.setBackgroundColor ( getResources ().getColor ( R.color.colorAccent ) );
//判断碎片是否为空 以免重复建立 影响性能
if (textFragment1 ==null) {
textFragment1 = new TextFragment1 ();
transaction.add ( R.id.scrollIndicatorDown , textFragment1 );
Log.i ( "空" , "为空" );
} else {
Log.i ( "空","不为空" );
transaction.show (textFragment1);
}
break;
case 1:
textView2.setTextColor ( Color.parseColor ( "#00574B" ) );
tvLayout2.setBackgroundColor ( getResources ().getColor ( R.color.colorAccent ) );
//判断碎片是否为空 以免重复建立 影响性能
if (textFragment2 ==null){
textFragment2 =new TextFragment2 ();
transaction.add ( R.id.scrollIndicatorDown, textFragment2 );
Log.i ( "空","为空" );
}else {
transaction.show ( textFragment2 );
Log.i ( "空","不为空" );
}
break;
case 2:
textView3.setTextColor ( Color.parseColor ( "#00574B" ) );
tvLayout3.setBackgroundColor ( getResources ().getColor ( R.color.colorAccent ) );
//判断碎片是否为空 以免重复建立 影响性能
if (textFragment3==null){
textFragment3=new TextFragment3 ();
transaction.add ( R.id.scrollIndicatorDown,textFragment3 );
Log.i ( "空","为空" );
}else {
transaction.show ( textFragment3 );
Log.i ( "空","不为空" );
}
break;
}
transaction.commit ();
}
隐藏碎片和设置tab样式
public void hideFragment2(FragmentTransaction transaction) {
//隐藏碎片 避免重叠
if (textFragment1 !=null){
transaction.hide ( textFragment1 );
}if (textFragment2 !=null){
transaction.hide ( textFragment2 );
}if (textFragment3!=null){
transaction.hide ( textFragment3 );
}
}
private void clearSelection() {
textView1.setTextColor ( Color.parseColor ( "#82858b" ) );
textView2.setTextColor ( Color.parseColor ( "#82858b" ) );
textView3.setTextColor ( Color.parseColor ( "#82858b" ) );
tvLayout1.setBackgroundColor ( getResources ().getColor ( R.color.colorPrimary ) );
tvLayout2.setBackgroundColor ( getResources ().getColor ( R.color.colorPrimary ) );
tvLayout3.setBackgroundColor ( getResources ().getColor ( R.color.colorPrimary ) );
}
本来想使用getid方法减少代码 但也是出现了错误 我就在view中查找对象并操作解决了问题
看来偷懒害死人 呜呜
原因是初始化控件在view中进行 大家都知道在activity中实现 但Fragment限制就是在view中查找对象 getActivity可能会出现空对象错误
private void setUI() {
tvLayout1.setOnClickListener ( new View.OnClickListener () {
@Override
public void onClick(View v) {
setTabSelection ( 0 );
}
} );
tvLayout2.setOnClickListener ( new View.OnClickListener () {
@Override
public void onClick(View v) {
setTabSelection ( 1 );
}
} );
tvLayout3.setOnClickListener ( new View.OnClickListener () {
@Override
public void onClick(View v) {
setTabSelection ( 2 );
}
} );
}
到这里第三个子类示范也已经结束 整体也已经写完 下面来看一下效果图和整体效果图
最后示例一下整体效果 由于本博主没有太好的录屏工具 左侧是可以滑动的
也给大家模拟一下内存回收的方法 在开发者选项中打开如下开关 当你隐藏前台进入后台就会模拟内存回收 旋转屏幕也可以生命周期重建 主要是生命周期出现的问题 不建议去掉缓存 一般还有人设置屏幕运行为竖屏 解决 但内存回收还是会出现bug 既然写软件就要写无bug的app
更新 解决fragment解决重叠后不显示的问题
地址https://github.com/surpreme/Fragment/blob/master/%E8%A7%A3%E5%86%B3%E4%B8%8D%E6%98%BE%E7%A4%BA%E7%9A%84%E9%97%AE%E9%A2%98
最后如果你觉得小编写的不错 希望给小编一个赞和指出错误 当然你也可以使用github上的fragmnetion框架
源码下载地址https://github.com/surpreme/Fragment/tree/master