标签: Fragment

android之对不同的页面管理

一、界面状态有哪些

在Android中,不管是activity或者fragment,在加载视图的时候都有可能会出现多种不同的状态页面View。比较多的有下面几种:

1、内容界面,也就是正常有数据页面

2、加载数据中,加载loading

3、加载数据错误,请求数据异常

4、加载后没有数据,请求数据为空

5、没有网络,网络异常

场景:

a、加载网络数据时,需要用户等待的场景,显示一个加载的Loading动画可以让用户知道App正在加载数据,而不是程序卡死,给用这样可以给户较好的使用体验。

b、当加载的数据为空时显示一个数据为空的视图、在数据加载失败时显示加载失败对应的UI并支持点击重试会比白屏的用户体验更好一些。

c、加载中、加载失败、空数据等不同状态页面风格,一般来说在App内的所有页面中需要保持一致,也就是需要做到全局统一。

二、采用include方式管理

直接把这些界面include到main界面中,然后动态去切换界面,具体一点的做法如下所示。

在布局中,会存放多个状态的布局。然后在页面中根据逻辑将对应的布局给显示或者隐藏,但存在诸多问题。

存在的问题分析

1、后来发现这样处理不容易复用到其他项目中,代码复用性很低

2、在activity中处理这些状态的显示和隐藏比较乱

3、调用setContentView方法时,是将所有的布局给加载绘制出来。其实没有必要

4、如果将逻辑写在BaseActivity中,利用子类继承父类特性,在父类中写切换状态,但有些界面如果没有继承父类,又该如何处理

三、在Base类中处理逻辑

1、定义一个自定义的控件,比如把它命名成LoadingView,然后在这个里面include一个布局,该布局包含一些不同状态的视图。代码思路如下所示:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
android:id=”@+id/activity_main”
android:orientation=”vertical”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<!–正常时布局–>
<include layout=”@layout/activity_content”/>
<!–加载loading布局–>
<include layout=”@layout/activity_loading”/>
<!–异常时布局–>
<include layout=”@layout/activity_error”/>
<!–空数据时布局–>
<include layout=”@layout/activity_emptydata”/>

</LinearLayout>
布局初始化

public class LoadingView extends LinearLayout implements View.OnClickListener {

public static final int LOADING = 0;
public static final int STOP_LOADING = 1;
public static final int NO_DATA = 2;
public static final int NO_NETWORK = 3;
public static final int GONE = 4;
public static final int LOADING_DIALOG = 5;

private TextView mNoDataTextView;
private ProgressBar mLoadingProgressBar;
private RelativeLayout mRlError;
private LinearLayout mLlLoading;
private View mView;

private OnRefreshListener mListener;

public void setRefrechListener(OnRefreshListener mListener) {
this.mListener = mListener;
}

public interface OnRefreshListener {
void refresh();
}

public LoadingView(Context context) {
super(context);
init(context);
}

public LoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

private void init(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mView = inflater.inflate(R.layout.common_loading_get, this);
mLoadingProgressBar = (ProgressBar) mView.findViewById(R.id.mLoadingProgressBar);
mNoDataTextView  = (TextView) mView.findViewById(R.id.mNoDataTextView);
mLlLoading = (LinearLayout) mView.findViewById(R.id.ll_loading);
mRlError = (RelativeLayout) mView.findViewById(R.id.rl_error);
mRlError.setOnClickListener(this);
setStatue(GONE);
}

public void setStatue(int status) {
setVisibility(View.VISIBLE);
try {
if (status == LOADING) {//更新
mRlError.setVisibility(View.GONE);
mLlLoading.setVisibility(View.VISIBLE);
} else if (status == STOP_LOADING) {
setVisibility(View.GONE);
} else if (status == NO_DATA) {//无数据情况
mRlError.setVisibility(View.VISIBLE);
mLlLoading.setVisibility(View.GONE);
mNoDataTextView.setText(“暂无数据”);
} else if (status == NO_NETWORK) {//无网络情况
mRlError.setVisibility(View.VISIBLE);
mLlLoading.setVisibility(View.GONE);
mNoDataTextView.setText(“网络加载失败,点击重新加载”);
} else {
setVisibility(View.GONE);
}
} catch (OutOfMemoryError e) {
}
}

@Override
public void onClick(View v) {
mListener.refresh();
setStatue(LOADING);
}
}
然后在BaseActivity/BaseFragment中封装LoadingView的初始化逻辑,并封装加载状态切换时的UI显示逻辑,暴露给子类以下方法:void showLoading(); //调用此方法显示加载中的动画

void showLoadFailed(); //调用此方法显示加载失败界面
void showEmpty(); //调用此方法显示空页面
void onClickRetry(); //子类中实现,点击重试的回调方法
2、在BaseActivity/BaseFragment的子类中可通过上一步的封装比较方便地使用加载状态显示功能。这种使用方式耦合度太高,每个页面的布局文件中都需要添加LoadingView,使用起来不方便而且维护成本较高,比如说有时候异常状态的布局各个页面不同,那么难以自定义处理,修改起来成本较高。

同时如果是要用这种状态管理工具,则需要在需要的页面布局中添加该LoadingView视图。这样也能够完成需求,但是感觉有点麻烦。

3、具体如何使用它进行状态管理呢?可以看到在对应的布局中需要写上LoadingView

<?xml version=”1.0″ encoding=”utf-8″?>
<RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<com.cheoo.app.view.recyclerview.TypeRecyclerView
android:id=”@+id/mRecyclerView”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:overScrollMode=”never”
android:scrollbars=”none”>

</com.cheoo.app.view.recyclerview.TypeRecyclerView>

<com.cheoo.app.view.LoadingView
android:id=”@+id/mLoadingView”
android:layout_width=”match_parent”
android:layout_height=”match_parent” />

</RelativeLayout>
那么,如果某个子类不想继承BaseActivity类,如何使用该状态管理器呢?代码中可以这种使用。

mLoadingView  = (LoadingView)findViewById(R.id.mLoadingView);
mLoadingView.setStatue(LoadingView.LOADING);
mLoadingView.setStatue(LoadingView.STOP_LOADING);
mLoadingView.setStatue(LoadingView.NO_NETWORK);
mLoadingView.setStatue(LoadingView.NO_DATA);
四、如何降低偶性和入侵性

1、让View状态的切换和Activity彻底分离开,必须把这些状态View都封装到一个管理类中,然后暴露出几个方法来实现View之间的切换。

2、在不同的项目中可以需要的View也不一样,所以考虑把管理类设计成builder模式来自由的添加需要的状态View。

3、那么如何降低耦合性,让代码入侵性低。方便维护和修改,且移植性强呢?大概具备这样的条件……

1)可以运用在activity或者fragment中

2)不需要在布局中添加LoadingView,而是统一管理不同状态视图,同时暴露对外设置自定义状态视图方法,方便UI特定页面定制

3)支持设置自定义不同状态视图,即使在BaseActivity统一处理状态视图管理,也支持单个页面定制

4)在加载视图的时候像异常和空页面能否用ViewStub代替,这样减少绘制,只有等到出现异常和空页面时,才将视图给inflate出来

5)当页面出现网络异常页面,空页面等,页面会有交互事件,这时候可以设置点击设置网络或者点击重新加载等等

五、封装低入侵性状态库

1、自定义帧布局

首先需要自定义一个状态StateFrameLayout布局,它是继承FrameLayout。在这个类中,目前是设置五种不同状态的视图布局,主要的功能操作是显示或者隐藏布局。为了后期代码维护性,根据面向对象的思想,类尽量保证单一职责,所以关于状态切换,以及设置自定义状态布局,把这个功能分离处理,放到一个StateLayoutManager中处理。

这个类的功能非常明确,就是隐藏或者展示视图作用。

/**
*
*     time  : 2019/71/9
*     desc  : 自定义帧布局
*     revise:
*
*/
public class StateFrameLayout extends FrameLayout {

/**
*  loading 加载id
*/
public static final int LAYOUT_LOADING_ID = 1;

/**
*  内容id
*/
public static final int LAYOUT_CONTENT_ID = 2;

/**
*  异常id
*/
public static final int LAYOUT_ERROR_ID = 3;

/**
*  网络异常id
*/
public static final int LAYOUT_NETWORK_ERROR_ID = 4;

/**
*  空数据id
*/
public static final int LAYOUT_EMPTY_DATA_ID = 5;

/**
*  存放布局集合
*/
private SparseArray<View> layoutSparseArray = new SparseArray<>();

//private HashMap<Integer,View> map = new HashMap<>();

/**
*  布局管理器
*/
private StateLayoutManager mStatusLayoutManager;

public StateFrameLayout(Context context) {
super(context);
}

public StateFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}

public StateFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

public void setStatusLayoutManager(StateLayoutManager statusLayoutManager) {
mStatusLayoutManager = statusLayoutManager;
//添加所有的布局到帧布局
addAllLayoutToRootLayout();
}

private void addAllLayoutToRootLayout() {
if (mStatusLayoutManager.contentLayoutResId != 0) {
addLayoutResId(mStatusLayoutManager.contentLayoutResId, StateFrameLayout.LAYOUT_CONTENT_ID);
}
if (mStatusLayoutManager.loadingLayoutResId != 0) {
addLayoutResId(mStatusLayoutManager.loadingLayoutResId, StateFrameLayout.LAYOUT_LOADING_ID);
}

if (mStatusLayoutManager.emptyDataVs != null) {
addView(mStatusLayoutManager.emptyDataVs);
}
if (mStatusLayoutManager.errorVs != null) {
addView(mStatusLayoutManager.errorVs);
}
if (mStatusLayoutManager.netWorkErrorVs != null) {
addView(mStatusLayoutManager.netWorkErrorVs);
}
}

private void addLayoutResId(@LayoutRes int layoutResId, int id) {
View resView = LayoutInflater.from(mStatusLayoutManager.context).inflate(layoutResId, null);
layoutSparseArray.put(id, resView);
addView(resView);
}

/**
*  显示loading
*/
public void showLoading() {
if (layoutSparseArray.get(LAYOUT_LOADING_ID) != null) {
showHideViewById(LAYOUT_LOADING_ID);
}
}

/**
*  显示内容
*/
public void showContent() {
if (layoutSparseArray.get(LAYOUT_CONTENT_ID) != null) {
showHideViewById(LAYOUT_CONTENT_ID);
}
}

/**
*  显示空数据
*/
public void showEmptyData(int iconImage, String textTip) {
if (inflateLayout(LAYOUT_EMPTY_DATA_ID)) {
showHideViewById(LAYOUT_EMPTY_DATA_ID);
emptyDataViewAddData(iconImage, textTip);
}
}

/**
* 根据ID显示隐藏布局
* @param id                        id值
*/
private void showHideViewById(int id) {
for (int i = 0; i < layoutSparseArray.size(); i++) {
int key = layoutSparseArray.keyAt(i);
View valueView = layoutSparseArray.valueAt(i);
//显示该view
if(key == id) {
valueView.setVisibility(View.VISIBLE);
if(mStatusLayoutManager.onShowHideViewListener != null) {
mStatusLayoutManager.onShowHideViewListener.onShowView(valueView, key);
}
} else {
if(valueView.getVisibility() != View.GONE) {
valueView.setVisibility(View.GONE);
if(mStatusLayoutManager.onShowHideViewListener != null) {
mStatusLayoutManager.onShowHideViewListener.onHideView(valueView, key);
}
}
}
}
}

/**
* 这个是处理ViewStub的逻辑,主要有网络异常布局,加载异常布局,空数据布局
* @param id                        布局id
* @return                          布尔值
*/
private boolean inflateLayout(int id) {
boolean isShow = true;
//如果为null,则直接返回false
if (layoutSparseArray.get(id) == null) {
return false;
}
switch (id) {
case LAYOUT_NETWORK_ERROR_ID:
if (mStatusLayoutManager.netWorkErrorVs != null) {
View view = mStatusLayoutManager.netWorkErrorVs.inflate();
retryLoad(view, mStatusLayoutManager.netWorkErrorRetryViewId);
layoutSparseArray.put(id, view);
isShow = true;
} else {
isShow = false;
}
break;
case LAYOUT_ERROR_ID:
if (mStatusLayoutManager.errorVs != null) {
View view = mStatusLayoutManager.errorVs.inflate();
if (mStatusLayoutManager.errorLayout != null) {
mStatusLayoutManager.errorLayout.setView(view);
}
retryLoad(view, mStatusLayoutManager.errorRetryViewId);
layoutSparseArray.put(id, view);
isShow = true;
} else {
isShow = false;
}
break;
case LAYOUT_EMPTY_DATA_ID:
if (mStatusLayoutManager.emptyDataVs != null) {
View view = mStatusLayoutManager.emptyDataVs.inflate();
if (mStatusLayoutManager.emptyDataLayout != null) {
mStatusLayoutManager.emptyDataLayout.setView(view);
}
retryLoad(view, mStatusLayoutManager.emptyDataRetryViewId);
layoutSparseArray.put(id, view);
isShow = true;
} else {
isShow = false;
}
break;
default:
break;
}
return isShow;
}
}
2、自定义状态管理器

上面状态的自定义布局创建出来了,且隐藏和展示都做了。

1、loadingLayoutResId和contentLayoutResId代表等待加载和显示内容的xml文件

2、几种异常状态要用ViewStub,因为在界面状态切换中loading和内容View都是一直需要加载显示的,但是其他的3个只有在没数据或者网络异常的情况下才会加载显示,所以用ViewStub来加载他们可以提高性能。

3、采用builder模式,十分简单,代码如下所示。创建StateFrameLayout对象,然后再设置setStatusLayoutManager,这一步操作是传递一个Manager对象到StateFrameLayout,建立连接。

public final class StateLayoutManager {

final Context context;

final int netWorkErrorRetryViewId;
final int emptyDataRetryViewId;
final int errorRetryViewId;
final int loadingLayoutResId;
final int contentLayoutResId;
final int retryViewId;
final int emptyDataIconImageId;
final int emptyDataTextTipId;
final int errorIconImageId;
final int errorTextTipId;

final ViewStub emptyDataVs;
final ViewStub netWorkErrorVs;
final ViewStub errorVs;
final AbsViewStubLayout errorLayout;
final AbsViewStubLayout emptyDataLayout;

private final StateFrameLayout rootFrameLayout;
final OnShowHideViewListener onShowHideViewListener;
final OnRetryListener onRetryListener;

public static Builder newBuilder(Context context) {
return new Builder(context);
}

private StateLayoutManager(Builder builder) {
this.context = builder.context;
this.loadingLayoutResId = builder.loadingLayoutResId;
this.netWorkErrorVs = builder.netWorkErrorVs;
this.netWorkErrorRetryViewId = builder.netWorkErrorRetryViewId;
this.emptyDataVs = builder.emptyDataVs;
this.emptyDataRetryViewId = builder.emptyDataRetryViewId;
this.errorVs = builder.errorVs;
this.errorRetryViewId = builder.errorRetryViewId;
this.contentLayoutResId = builder.contentLayoutResId;
this.onShowHideViewListener = builder.onShowHideViewListener;
this.retryViewId = builder.retryViewId;
this.onRetryListener = builder.onRetryListener;
this.emptyDataIconImageId = builder.emptyDataIconImageId;
this.emptyDataTextTipId = builder.emptyDataTextTipId;
this.errorIconImageId = builder.errorIconImageId;
this.errorTextTipId = builder.errorTextTipId;
this.errorLayout = builder.errorLayout;
this.emptyDataLayout = builder.emptyDataLayout;

//创建帧布局
rootFrameLayout = new StateFrameLayout(this.context);
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
rootFrameLayout.setLayoutParams(layoutParams);

//设置状态管理器
rootFrameLayout.setStatusLayoutManager(this);
}

/**
* 显示loading
*/
public void showLoading() {
rootFrameLayout.showLoading();
}

/**
* 显示内容
*/
public void showContent() {
rootFrameLayout.showContent();
}

/**
* 显示空数据
*/
public void showEmptyData(int iconImage, String textTip) {
rootFrameLayout.showEmptyData(iconImage, textTip);
}

/**
* 显示空数据
*/
public void showEmptyData() {
showEmptyData(0, “”);
}

/**
* 显示空数据
*/
public void showLayoutEmptyData(Object… objects) {
rootFrameLayout.showLayoutEmptyData(objects);
}

/**
* 显示网络异常
*/
public void showNetWorkError() {
rootFrameLayout.showNetWorkError();
}

/**
* 显示异常
*/
public void showError(int iconImage, String textTip) {
rootFrameLayout.showError(iconImage, textTip);
}

/**
* 显示异常
*/
public void showError() {
showError(0, “”);
}

public void showLayoutError(Object… objects) {
rootFrameLayout.showLayoutError(objects);
}

/**
* 得到root 布局
*/
public View getRootLayout() {
return rootFrameLayout;
}
Builder类

public static final class Builder {

private Context context;
private int loadingLayoutResId;
private int contentLayoutResId;
private ViewStub netWorkErrorVs;
private int netWorkErrorRetryViewId;
private ViewStub emptyDataVs;
private int emptyDataRetryViewId;
private ViewStub errorVs;
private int errorRetryViewId;
private int retryViewId;
private int emptyDataIconImageId;
private int emptyDataTextTipId;
private int errorIconImageId;
private int errorTextTipId;
private AbsViewStubLayout errorLayout;
private AbsViewStubLayout emptyDataLayout;
private OnShowHideViewListener onShowHideViewListener;
private OnRetryListener onRetryListener;

Builder(Context context) {
this.context = context;
}

/**
* 自定义加载布局
*/
public Builder loadingView(@LayoutRes int loadingLayoutResId) {
this.loadingLayoutResId = loadingLayoutResId;
return this;
}

/**
* 自定义网络错误布局
*/
public Builder netWorkErrorView(@LayoutRes int newWorkErrorId) {
netWorkErrorVs = new ViewStub(context);
netWorkErrorVs.setLayoutResource(newWorkErrorId);
return this;
}

/**
* 自定义加载空数据布局
*/
public Builder emptyDataView(@LayoutRes int noDataViewId) {
emptyDataVs = new ViewStub(context);
emptyDataVs.setLayoutResource(noDataViewId);
return this;
}

/**
* 自定义加载错误布局
*/
public Builder errorView(@LayoutRes int errorViewId) {
errorVs = new ViewStub(context);
errorVs.setLayoutResource(errorViewId);
return this;
}

/**
* 自定义加载内容正常布局
*/
public Builder contentView(@LayoutRes int contentLayoutResId) {
this.contentLayoutResId = contentLayoutResId;
return this;
}

public Builder errorLayout(AbsViewStubLayout errorLayout) {
this.errorLayout = errorLayout;
this.errorVs = errorLayout.getLayoutVs();
return this;
}

public Builder emptyDataLayout(AbsViewStubLayout emptyDataLayout) {
this.emptyDataLayout = emptyDataLayout;
this.emptyDataVs = emptyDataLayout.getLayoutVs();
return this;
}

public Builder netWorkErrorRetryViewId(@LayoutRes int netWorkErrorRetryViewId) {
this.netWorkErrorRetryViewId = netWorkErrorRetryViewId;
return this;
}

public Builder emptyDataRetryViewId(@LayoutRes int emptyDataRetryViewId) {
this.emptyDataRetryViewId = emptyDataRetryViewId;
return this;
}

public Builder errorRetryViewId(@LayoutRes int errorRetryViewId) {
this.errorRetryViewId = errorRetryViewId;
return this;
}

public Builder retryViewId(@LayoutRes int retryViewId) {
this.retryViewId = retryViewId;
return this;
}

public Builder emptyDataIconImageId(@LayoutRes int emptyDataIconImageId) {
this.emptyDataIconImageId = emptyDataIconImageId;
return this;
}

public Builder emptyDataTextTipId(@LayoutRes int emptyDataTextTipId) {
this.emptyDataTextTipId = emptyDataTextTipId;
return this;
}

public Builder errorIconImageId(@LayoutRes int errorIconImageId) {
this.errorIconImageId = errorIconImageId;
return this;
}

public Builder errorTextTipId(@LayoutRes int errorTextTipId) {
this.errorTextTipId = errorTextTipId;
return this;
}

/**
* 为状态View显示隐藏监听事件
* @param listener                  listener
* @return
*/
public Builder onShowHideViewListener(OnShowHideViewListener listener) {
this.onShowHideViewListener = listener;
return this;
}

/**
* 为重试加载按钮的监听事件
* @param onRetryListener           listener
* @return
*/
public Builder onRetryListener(OnRetryListener onRetryListener) {
this.onRetryListener = onRetryListener;
return this;
}

/**
* 创建对象
* @return
*/
public StateLayoutManager build() {
return new StateLayoutManager(this);
}
}

}
3、如何管理多种状态

大约5种状态,如何管理这些状态?添加到集合中,Android中选用SparseArray比HashMap更省内存,在某些条件下性能更好,主要是因为它避免了对key的自动装箱(int转为Integer类型),它内部则是通过两个数组来进行数据存储的,一个存储key,另外一个存储value,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间

/**存放布局集合 */
private SparseArray<View> layoutSparseArray = new SparseArray();

/**将布局添加到集合 */
private void addLayoutResId(@LayoutRes int layoutResId, int id) {
View resView = LayoutInflater.from(mStatusLayoutManager.context).inflate(layoutResId, null);
layoutSparseArray.put(id, resView);
addView(resView);
}

//那么哪里从集合中取数据呢
public void showContent() {
if (layoutSparseArray.get(LAYOUT_CONTENT_ID) != null) {
showHideViewById(LAYOUT_CONTENT_ID);
}
}
-END

————————————————
版权声明:本文为CSDN博主「generallizhong」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/generallizhong/article/details/103894884

安卓性能优化之Fragment中数据的懒加载

1、背景:为什么需要懒加载。

我们在做安卓项目的时候,经常会有一个使用场景:ViewPage与多个Fragment组合使用。

%title插图%num

 

然而,viewpager有着预加载机制:默认一次加载当前页面前后两个页面,即使设置setOffLimit(0)也没有效果。  虽然预加载优化了app的体验效果,但是这样把我们看不到的页面的数据也加载了,大大降低了性能,浪费初始化资源。

这时候,我们就需要懒加载。

 

2、什么是懒加载:懒加载的定义。

当页面可见的时候,才加载当前页面。 没有打开的页面,就不会预加载。

说白了,懒加载就是可见的时候才去请求数据。

 

3、实现方法

(1)关键点

主要的方法是Fragment中的setUserVisibleHint(),此方法会在onCreateView()之前执行,当viewPager中fragment改变可见状态时也会调用,当fragment 从可见到不见,或者从不可见切换到可见,都会调用此方法,使用getUserVisibleHint() 可以返回fragment是否可见状态。

(2)注意事项

<1>添加isPrepared参数。在系统调用onActivityCreated时设置为true,这时onCreateView方法已调用完毕(一般我们在这方法里执行findviewbyid等方法),确保 onLazyLoad()方法不会报空指针异常。

<2>添加isLazyLoaded参数。确保ViewPager来回切换时BaseFragment的initData方法不会被重复调用,onLazyLoad在该Fragment的整个生命周期只调用一次,*次调用onLazyLoad()方法后马上执行 isLazyLoaded = true。

<3>getUserVisibleHint()会返回是否可见状态,这是fragment实现懒加载的关键,只有fragment 可见才会调用onLazyLoad() 加载数据。

 

(3)demo

%title插图%num
懒加载fragment基类

评论:

  • 码哥%title插图%numJaynm:兄弟,你这个还是有bug阿,如果跳页面切换时,还是会自动加载左右2个界面10 月前回复%title插图%num
    • 书中有颜如玉回复:我是这样处理的,这都过五个月了,你这还没处理吗5 月前回复%title插图%num
    • 书中有颜如玉回复:好久不见啊5 月前回复%title插图%num
    • 书中有颜如玉回复:

      1. /** Fragment当前状态是否可见 */
      2. protected boolean isVisible=false;
      3. @Override
      4. public void setUserVisibleHint(boolean isVisibleToUser) {
      5. super.setUserVisibleHint(isVisibleToUser);
      6. if(getUserVisibleHint()) {//可见
      7. isVisible = true;
      8. onVisible();
      9. } else {//不可见
      10. isVisible = false;
      11. onInvisible();
      12. }
      13. }

      5 月前回复%title插图%num

    • 码哥%title插图%numJaynm回复书中有颜如玉:有解决方案5 月前回复%title插图%num
    • 书中有颜如玉回复:和你遇到一样的问题了7 月前回复%title插图%num
    • 书中有颜如玉回复:你这个问题处理了吗7 月前回复

Android开发之多Fragment切换优化

问题分析

 


 

一直在简书里看别人的技术贴,今天我也来写点自己的心得!*近在写一个项目用到大量的Fragment后的总结!

我想刚刚接触安卓的同学或许会这么写:

  1. FragmentManager     fragmentManager=getSupportFragmentManager();
  2. FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
  3. fragmentTransaction.add(ViewId,fragment);// 或者fragmentTransaction.replace(ViewId,fragment);
  4. fragmentTransaction.commit();

基础更好一点的同学会用show和hide方法

  1.  FragmentManager fm = getSupportFragmentManager();
  2. FragmentTransaction ft = fm.beginTransaction();
  3. ft.hide(new FirstFragment())
  4.        .show(new SecondFragment())
  5.        .commit();

诚然这两种都可以切换Fragment,但是面对用户大量点击来回切换,或者你的Fragment本来就很多,每次都这样操作,那么很快你的应用就会OOM,就算不崩那也会异常的卡顿!so why?

当我们replace时发生了以下的生命周期:

 

%title插图%num

想想看每次都replace一下!!这世界会有多美好!!!那么问题出在哪?回过头看看代码就会发现每次在add/replace或者show/hide都会new 一个新的实例,这就是致命原因!!!!!

废话少说,开始优化

 


 

方案一:

预加载模式:

  1. //首先需要先实例好三个全局Fragment
  2. FragmentManager fm = getSupportFragmentManager();
  3. FragmentTransaction ft = fm.beginTransaction();
  4. ft.add(R.id.fragment, FirstFragment.getInstance());
  5. ft.add(R.id.fragment, SecondFragment.getInstance());
  6. ft.add(R.id.fragment, ThirdFragment.getInstance());
  7. ft.hide(SecondFragment.getInstance());
  8. ft.hide(ThirdFragment.getInstance());
  9. ft.commit();

在加载*个Fragment时就把全部Fragment加载好,下次使用直接调用如:

  1. FragmentManager fm = getSupportFragmentManager();
  2. FragmentTransaction ft = fm.beginTransaction();
  3. ft.hide(FirstFragment.getInstance())
  4.        .show(SecondFragment.getInstance())
  5.        .commit();

是不是总觉怪怪的,虽然比之前的代码好,但是这种做法很Java,当然需要预加载的朋友依然是不二之选!!!

那有没有更好的方法呢?答案是肯定的

方案二:

动态加载模式:

 


//首先需要先实例好n个全局Fragment

//private  Fragment  currentFragment=new Fragment();(全局)

 

private  FragmentTransaction switchFragment(Fragment targetFragment) {    FragmentTransaction transaction = getSupportFragmentManager()            .beginTransaction();    if (!targetFragment.isAdded()) {        //*次使用switchFragment()时currentFragment为null,所以要判断一下        if (currentFragment != null) {            transaction.hide(currentFragment);            }        transaction.add(R.id.fragment, targetFragment,targetFragment.getClass().getName());        } else {            transaction                    .hide(currentFragment)                    .show(targetFragment);        }        currentFragment = targetFragment;       return   transaction;    }

在点击切换Fragment时:

  1. @Override
  2. public void onTabSelected(@IdRes int tabId) {
  3.        if (tabId == R.id.tab_one){
  4.            switchFragment(first).commit();
  5.        }
  6.        if (tabId == R.id.tab_two){
  7.            switchFragment(second).commit();
  8.        }
  9.        if (tabId == R.id.tab_three){
  10.            switchFragment(third).commit();
  11.        }
  12.    }

现在你的Fragment无论怎么切都不会出现卡顿了,因为你的所有Fragment只会被实例化一次!实例一次的Fragment会被存入内存中,下次切换会判断内存中是否含有要切换的Fragment,如果有就直接复用,没有就add一个新的!优化大法完成!

外番

 


 

WHAT?等等!只实例一次,那我的Fragment里的数据要更新怎么办?我的回答是——软件关了再次重启!

 

%title插图%num

要是这样,这样的软件真的要逆天了!好在官方提供了onHiddenChanged方法,每次切换hide或者show时该方法会被执行,可以在这里面更新数据!


//此方法在Fragment中

@Override public void onHiddenChanged(boolean hidden) {    super.onHiddenChanged(hidden);    if (hidden){       //Fragment隐藏时调用    }else {        //Fragment显示时调用    } }

此方法是不是比每次add或replace更新数据执行一大坨的生命周期要优雅的多的多!

使用ViewPager + Fragment实现滑动菜单Tab效果 –简易版

描述:

之前有做过一个记账本APP,拿来练手的,做的很简单,是用Eclipse开发的;

*近想把这个APP重新完善一下,添加了一些新的功能,并选用Android Studio来开发;

APP已经完善了一部分,现在就想把已经做好的功能整理一下,记录下来。

 

效果图:

可以手动滑动菜单

也可以通过点击头部菜单进行切换

%title插图%num         %title插图%num

 

项目效果图:

%title插图%num %title插图%num

 

具体实现的代码:

前台代码(activity_main.xml):

%title插图%num View Code

 

主界面代码(MainActivity.java):

%title插图%num View Code

 

需要多少个Fragment,便创建多少个,这里只举例写一个,其它相同

建立Fragment(fragment_one.xml):

%title插图%num View Code

 

Fragment界面代码(OneFragment.java):

%title插图%num View Code

 

strings.xml:

<string name="detail_tab">明细</string>
<string name="category_report_tab">类别报表</string>

 

colors.xml:

<color name="main_tab_text_color">#000000</color>

 

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速