1.使用Fragment代替Activity来显示页面
我们都知道Activity的启动和销毁需要进行view的创建和销毁以及其他资源的创建与销毁(比如:BroadCaster, 数据库, 网络等), 这将花费一定的时间,这是导致页面启动显示慢的原因之一.如果使用Fragment来代替Activity来显示页面,避开Activity的创建和销毁,可以显著的提高用户体验,这也是现在绝大部分应用采用的做法.废话不多说,上代码:
首先我们要定义一个BaseActivity用来处理一些activity公共的操作,请看代码:
/**
* @author kevin
*/
public abstract class BaseActivity extends AppCompatActivity {
public Activity activity;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
doBeforeSetContentView();
setContentView(getLayoutId());
doAfterSetContentView();
initPresenter();
initView(savedInstanceState);
}
@SuppressLint("SourceLockedOrientationActivity")
private void doBeforeSetContentView() {
// 把activity放到application栈中管理
AppManager.getAppManager().addActivity(this);
// 设置全屏
setFullScreen();
// 设置竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
private void doAfterSetContentView() {
//点击activity中除键盘任意其他的地方可收回键盘
HideIMEUtil.wrap(this);
activity = this;
}
/**
* 获取布局文件
*
* @return 返回具体fragment对应的Id
*/
protected abstract @LayoutRes
int getLayoutId();
//简单页面无需mvp就不用管此方法即可,完美兼容各种实际场景的变通
public abstract void initPresenter();
//初始化view
public abstract void initView(@Nullable Bundle savedInstanceState);
public void setFullScreen() {
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
@Override
protected void onDestroy() {
super.onDestroy();
AppManager.getAppManager().finishActivity(this);
}
}
我们注意到上面的getLayoutId()方法,这个方法由个特定的activity来override, 比方我们来看看其中一个activity, ContainterActivity.
/**
* @author kevin //只是一个容器activtiy,用来装载各种fragement
*/
public class ContainerActivity extends BaseActivity {
...
@Override
protected int getLayoutId() {
return R.layout.activity_container;
}
@Override
public void initPresenter() {
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void initView(@Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
mainFragment = (MainFragment) getSupportFragmentManager().getFragment(savedInstanceState, Constants.MAIN_FRAGMENT);
if (mainFragment == null) {
createAndAddMainFragment();
}
lightFragment = (LightFragment) getSupportFragmentManager().getFragment(savedInstanceState, Constants.LIGHT_FRAGMENT);
if (lightFragment == null) {
createAndAddLightFragment();
}
cameraFragment = (CameraFragment) getSupportFragmentManager().getFragment(savedInstanceState, Constants.CAMERA_FRAGMENT);
if (cameraFragment == null) {
createAndAddCameraFragment();
}
deashingFragment = (DeashingFragment) getSupportFragmentManager().getFragment(savedInstanceState, Constants.DEASHING_FRAGMENT);
if (deashingFragment == null) {
createAndAddDeashingFragment();
}
sortingFragment = (SortingFragment) getSupportFragmentManager().getFragment(savedInstanceState, Constants.SORTING_FRAGMENT);
if (sortingFragment == null) {
createAndAddSortingFragment();
}
vibratorFragment = (VibratorFragment) getSupportFragmentManager().getFragment(savedInstanceState, Constants.VIBRATOR_FRAGMENT);
if (vibratorFragment == null) {
createAndAddVibratorFragment();
}
downloadFragment = (DownloadFragment) getSupportFragmentManager().getFragment(savedInstanceState, Constants.DOWNLOAD_FRAGMENT);
if (downloadFragment == null) {
createAndAddDownloadFragment();
}
} else {
createAndAddMainFragment();
}
showOrHideFragment(Constants.MAIN_FRAGMENT);
...
}
/**
* 显示和隐藏fragment的状态机
*/
public void showOrHideFragment(String fragmentTab) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (mainFragment != null) {
if (fragmentTab.equals(Constants.MAIN_FRAGMENT)) {
fragmentTransaction.show(mainFragment);
} else {
fragmentTransaction.hide(mainFragment);
}
}
if (lightFragment != null) {
if (fragmentTab.equals(Constants.LIGHT_FRAGMENT)) {
fragmentTransaction.show(lightFragment);
} else {
fragmentTransaction.hide(lightFragment);
}
}
if (cameraFragment != null) {
if (fragmentTab.equals(Constants.CAMERA_FRAGMENT)) {
fragmentTransaction.show(cameraFragment);
} else {
fragmentTransaction.hide(cameraFragment);
}
}
if (deashingFragment != null) {
if (fragmentTab.equals(Constants.DEASHING_FRAGMENT)) {
fragmentTransaction.show(deashingFragment);
} else {
fragmentTransaction.hide(deashingFragment);
}
}
if (sortingFragment != null) {
if (fragmentTab.equals(Constants.SORTING_FRAGMENT)) {
fragmentTransaction.show(sortingFragment);
} else {
fragmentTransaction.hide(sortingFragment);
}
}
if (vibratorFragment != null) {
if (fragmentTab.equals(Constants.VIBRATOR_FRAGMENT)) {
fragmentTransaction.show(vibratorFragment);
} else {
fragmentTransaction.hide(vibratorFragment);
}
}
if (downloadFragment != null) {
if (fragmentTab.equals(Constants.DOWNLOAD_FRAGMENT)) {
fragmentTransaction.show(downloadFragment);
} else {
fragmentTransaction.hide(downloadFragment);
}
}
fragmentTransaction.commit();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
if (intent != null) {
String tag;
tag = intent.getStringExtra(Constants.BUNDLE_KEY_WHERE_TO_GO);
LogUtil.d(TAG, " tag : " + tag);
if (null != tag && tag.length() > 1) {
switch (tag) {
case Constants.CAMERA_FRAGMENT:
createAndAddCameraFragment();
currentTab = Constants.CAMERA_FRAGMENT;
break;
case Constants.LIGHT_FRAGMENT:
createAndAddLightFragment();
currentTab = Constants.LIGHT_FRAGMENT;
break;
case Constants.DEASHING_FRAGMENT:
createAndAddDeashingFragment();
currentTab = Constants.DEASHING_FRAGMENT;
break;
case Constants.DOWNLOAD_FRAGMENT:
createAndAddDownloadFragment();
currentTab = Constants.DOWNLOAD_FRAGMENT;
break;
case Constants.SORTING_FRAGMENT:
createAndAddSortingFragment();
currentTab = Constants.SORTING_FRAGMENT;
break;
case Constants.VIBRATOR_FRAGMENT:
createAndAddVibratorFragment();
currentTab = Constants.VIBRATOR_FRAGMENT;
break;
default:
createAndAddMainFragment();
currentTab = Constants.MAIN_FRAGMENT;
break;
}
showOrHideFragment(currentTab);
}
}
}
private void createAndAddMainFragment() {
if (mainFragment == null) {
mainFragment = MainFragment.newInstance();
}
if (!mainFragment.isAdded()) {
getSupportFragmentManager().beginTransaction().add(R.id.frame_content, mainFragment, MainFragment.class.getName()).commit();
}
}
private void createAndAddCameraFragment() {
if (cameraFragment == null) {
cameraFragment = CameraFragment.newInstance();
}
if (!cameraFragment.isAdded()) {
getSupportFragmentManager().beginTransaction().add(R.id.frame_content, cameraFragment, CameraFragment.class.getName()).commit();
}
}
private void createAndAddLightFragment() {
if (lightFragment == null) {
lightFragment = LightFragment.newInstance();
}
if (!lightFragment.isAdded()) {
getSupportFragmentManager().beginTransaction().add(R.id.frame_content, lightFragment, LightFragment.class.getName()).commit();
}
}
private void createAndAddDeashingFragment() {
if (deashingFragment == null) {
deashingFragment = DeashingFragment.newInstance();
}
if (!deashingFragment.isAdded()) {
getSupportFragmentManager().beginTransaction().add(R.id.frame_content, deashingFragment, DeashingFragment.class.getName()).commit();
}
}
private void createAndAddDownloadFragment() {
if (downloadFragment == null) {
downloadFragment = DownloadFragment.newInstance();
}
if (!downloadFragment.isAdded()) {
getSupportFragmentManager().beginTransaction().add(R.id.frame_content, downloadFragment, DownloadFragment.class.getName()).commit();
}
}
private void createAndAddSortingFragment() {
if (sortingFragment == null) {
sortingFragment = SortingFragment.newInstance();
}
if (!sortingFragment.isAdded()) {
getSupportFragmentManager().beginTransaction().add(R.id.frame_content, sortingFragment, SortingFragment.class.getName()).commit();
}
}
private void createAndAddVibratorFragment() {
if (vibratorFragment == null) {
vibratorFragment = VibratorFragment.newInstance();
}
if (!vibratorFragment.isAdded()) {
getSupportFragmentManager().beginTransaction().add(R.id.frame_content, vibratorFragment, VibratorFragment.class.getName()).commit();
}
}
@Override
public void onBackPressed() {
//如果是主页面就退出
if (currentTab.equals(Constants.MAIN_FRAGMENT)) {
DialogUtil.createAlertDialog(this, "", StringUtil.getString(R.string.really_want_exit));
} else {
switch (currentTab) {
case Constants.CAMERA_FRAGMENT:
if (cameraFragment != null) {
if (cameraFragment.onBackPressed()) {
break;
} else {
return;
}
}
break;
case Constants.LIGHT_FRAGMENT:
if (lightFragment != null) {
if (lightFragment.onBackPressed()) {
break;
} else {
return;
}
}
break;
case Constants.DEASHING_FRAGMENT:
if (deashingFragment != null) {
if (deashingFragment.onBackPressed()) {
break;
}else {
return;
}
}
break;
case Constants.DOWNLOAD_FRAGMENT:
if (downloadFragment != null) {
if (downloadFragment.onBackPressed()) {
break;
}else {
return;
}
}
break;
case Constants.SORTING_FRAGMENT:
if (sortingFragment != null) {
if (sortingFragment.onBackPressed()) {
break;
}else {
return;
}
}
break;
case Constants.VIBRATOR_FRAGMENT:
if (vibratorFragment != null) {
if (vibratorFragment.onBackPressed()) {
break;
}else {
return;
}
}
break;
default:
break;
}
currentTab = Constants.MAIN_FRAGMENT;
showOrHideFragment(currentTab);
}
}
}
为什么我用Contanter来命名,是因为这个activity像个容器一样可以装着 MainFragment, LightFragment,CameraFragment等一个Fragment,然后控制他们的切换,那么ContainerActivity的布局文件中又有什么呢?我们来看一下:
点进去,
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/frame_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
咦,就是一个简单的FrameLayout,那么界面的布局在哪儿写呢? 别慌各位看客们,估计聪明的你肯定想到写到Fragment每个对应的布局文件中去了, Bingo, 对的就是在那里,我们一起去看看吧. 别慌先看看代码的组织结构,这样方便理解.
Java代码结构如下:
Layout文件结构如下:
那么fragment_main的具体代码如下:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
android:background="@color/turquoise"
android:orientation="vertical"
tools:context=".fragment.sub.MainFragment">
<Spinner
android:id="@+id/language_choice"
style="@style/language_choice_style"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Spinner
android:id="@+id/machine_type"
style="@style/machine_type_style"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/language_choice" />
<TextView
android:id="@+id/version_number"
style="@style/main_textview_style"
android:text="@string/version_number"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.xxx.drag.DragFlowLayout
android:id="@+id/drag_flowLayout"
android:layout_width="800dp"
android:layout_height="500dp"
android:layout_marginStart="208dp"
android:layout_marginLeft="208dp"
android:layout_marginTop="192dp"
app:flowLayout_horizontal_space="50dp"
app:flowLayout_vertical_space="50dp"
app:flowLayout_maxLine="2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded" />
<com.xxx.colorsorter.ui.button.FlowingDrawableButton
android:id="@+id/company_copyright"
style="@style/company_copyright_style"
app:drawableLeft="@drawable/logo"
app:drawableHeight="46dp"
app:drawableWidth="150dp"
android:text="@string/company_copyright"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
那么这布局怎么加载的呢?那么来看看BaseFragment.如下:
/**
* @author kevin
*/
public abstract class BaseFragment extends Fragment {
private static final String TAG = BaseFragment.class.getSimpleName();
protected View containerView;
protected boolean hasViewCreated = false;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
containerView = inflater.inflate(getLayoutId(), container, false);
return containerView;
}
/**
* 获取布局文件
*
* @return the id of concrete fragment
*/
protected abstract @LayoutRes
int getLayoutId();
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
//不在最前端界面显示
if (hidden) {
onHide();
} else if (hasViewCreated) {
onShow();
}
}
@Override
public void onDestroy() {
super.onDestroy();
}
/**
* 当这个fragment显示时调用
*/
public abstract void onShow();
/**
* 获取数据模型
*/
protected abstract void getModel();
/**
* 当这个fragment隐藏时调用
*/
protected abstract void onHide();
/**
* 当用户按返回键时调用,返回true时,可以退出当前的fragment,否则的话,还是当前fragment
*/
public boolean onBackPressed() {
}
/**
* 保存
*/
public abstract void save();
/**
* 刷新
*/
public void refreshUi() {
}
/**
* 通过Class跳转界面
**/
public void startActivity(Class<?> cls) {
startActivity(cls, null);
}
/**
* 通过Class跳转界面
**/
public void startActivityForResult(Class<?> cls, int requestCode) {
startActivityForResult(cls, null, requestCode);
}
/**
* 含有Bundle通过Class跳转界面
**/
public void startActivityForResult(Class<?> cls, Bundle bundle,
int requestCode) {
Intent intent = new Intent();
intent.setClass(getActivity(), cls);
if (bundle != null) {
intent.putExtras(bundle);
}
startActivityForResult(intent, requestCode);
}
}
BaseFragment就是对各个fragment要用的公共方法的一个抽取. 所以,MainFragment的界面的具体加载自然在MainFragment.java中.我们来看看吧:
/**
* @author kevin
*/
public class MainFragment extends BaseFragment {
...
/**
* onCreateView has already called in super class
*/
@Override
protected int getLayoutId() {
return R.layout.fragment_main;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
...
initDrawFlowLayout(view);
...
}
private void initDrawFlowLayout(@NonNull View view) {
...
dragFlowLayout.setOnItemClickListener(new DragFlowLayout.OnItemClickListener() {
@Override
public boolean performClick(DragFlowLayout dragFlowLayout, View child, MotionEvent event, int dragState) {
DrawableButton drawableButton = child.findViewById(R.id.drawable_button);
switch (event.getAction()) {
...
case MotionEvent.ACTION_UP:
int tag = (int) drawableButton.getTag();
switch (tag) {
case R.string.sorting:
sortingOnClick();
break;
case R.string.camera_setting:
cameraSettingOnClick();
break;
case R.string.deashing_setting:
deashingSettingOnClick();
break;
case R.string.vibrator_setting:
vibratorSettingOnClick();
break;
case R.string.light_setting:
lightSettingOnClick();
break;
case R.string.download_setting:
downloadSettingOnClick();
break;
default:
break;
}
break;
}
return true;
}
});
...
}
@Override
public void onShow() {
...
}
@Override
protected void onHide() {
...
}
@Override
protected void getModel() {
...
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
...
}
@Override
public boolean onBackPressed() {
save();
return super.onBackPressed();
}
@Override
public void save() {
...
}
private void lightSettingOnClick() {
startActivitySingleton(Constants.LIGHT_FRAGMENT);
}
private void deashingSettingOnClick() {
startActivitySingleton(Constants.DEASHING_FRAGMENT);
}
private void cameraSettingOnClick() {
startActivitySingleton(Constants.CAMERA_FRAGMENT);
}
private void downloadSettingOnClick() {
startActivitySingleton(Constants.DOWNLOAD_FRAGMENT);
}
private void vibratorSettingOnClick() {
startActivitySingleton(Constants.VIBRATOR_FRAGMENT);
}
private void sortingOnClick() {
startActivitySingleton(Constants.SORTING_FRAGMENT);
}
}
还有一个问题,fragment是如何切换的呢?看上面 initDrawFlowLayout方法,一个switch,相信不用我废话,你就明白了吧.
2. 使用懒加载的方式
就是用户访问到的页面才创建和加载,比如上面的fragment,一开始用户进入主界面,就创建和加载主界面,其他的fragment都不创建和加载,如 在initView 中 createAndAddMainFragment. 数据的加载使用异步加载的方式,比较好些. 有些界面的view可以使用viewStub来占坑.
3.使用ConstraintLayout来减少布局的深度
我们知道以前,布局喜欢用LinearLayout不断的嵌套,这样导致xml的dom树很深,而xml的解析,一般会用到递归算法,这样严重影响性能.除了使用ConstraintLayout外,很多情况下使用<merge></merge>来代替LinearLayout来减少布局深度.比如如下:
<?xml version="1.0" encoding="utf-8"?>
<merge 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"
tools:context=".fragment.sub.CameraFragment">
<com.xxx.colorsorter.ui.scroll.ObservableScrollView
android:id="@+id/camera_image_scrollview"
android:layout_width="1280px"
android:layout_height="256px"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:layout_width="1280px"
tools:layout_height="256px"
tools:ignore="PxUsage">
<ImageView
android:id="@+id/camera_image"
android:layout_width="2048px"
android:layout_height="256px"
android:adjustViewBounds="true"
android:contentDescription="@string/image_from_camera"
tools:ignore="PxUsage"
tools:layout_height="200dp"
tools:layout_width="match_parent"
tools:scaleType="fitXY" />
</com.xxx.colorsorter.ui.scroll.ObservableScrollView>
<RelativeLayout
android:id="@+id/slide_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="@id/camera_image_scrollview"
app:layout_constraintRight_toRightOf="@id/camera_image_scrollview"
app:layout_constraintTop_toBottomOf="@id/camera_image_scrollview">
<ImageView
android:id="@+id/slide_ind"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_centerInParent="true"
android:background="@drawable/bg_anim_up_arrow"
android:contentDescription="@string/slide_indicator"
android:scaleType="fitXY"
android:visibility="invisible" />
<TextView
android:id="@+id/color_ind"
style="@style/camera_textview_style2"
android:layout_marginEnd="150dp" />
<TextView
android:id="@+id/hsi_ind"
style="@style/camera_textview_style2"
android:layout_marginEnd="5dp" />
</RelativeLayout>
</merge>
写完了,希望各位看客补充.