现在的App或多或少都会用到加载动画,那么如何将多样性的加载动画集成到我们的页面框架中呢?
一些实现方式:
方式一:将加载动画封装成自定义View,在布局中进行添加。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_project_select_designer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LoadingProgress
android:id="@+id/loading_progress"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
这样的实现方式,需要在每一个使用加载动画的布局文件中添加LoadingProgress,并且在界面调用时控制显示逻辑。如果大面积的使用到重复的代码,我们可以对代码进行封装,例如封装成一个RecyclerWithLoadAnimView,在该类中进行对LoadingProgress的显示控制。
但是问题来了,如果有特定需求需要继承RecyclerView实现特定功能呢?
所以我们还是不这样写了,为了解耦合,我们还是讲LoadingProgress放在别的地方去写。
方式二:在BaseActivity中添加LoadingProgress
那么问题来了,我们怎么在不通过布局文件,并且不影响子类的情况下,添加LoadingProgress呢?
其实很简单,DecorView大家都知道吧,这是我们在onCreate() 的setContentView()方法返回的布局的一个父容器。我们可以直接将LoadingProgress添加到布局的一个父容器中就可以了,然后在BaseActivity中对LoadingProgress进行逻辑控制。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(...);
}
现在我们找一下这个DecorView。大家都知道,我们视图都会放在Window里,而Window类却是一个抽象类,所以我们要找到它的子类,PhoneWindow类,就是这个类,我们的setContentView方法最终会调用PhoneWindow类中的
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...
}
多余的代码我们不用看,我们只需要看installDecor()方法,这就是创建DecorView的方,我们看一下:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
private final class DecorView extends FrameLayout{
...
}
这里我们看到generateDecor()方法,这只是创建了一个DecorView,不过我们可以知道DecorView是FrameLayout的子类。
但是我们没有找到对我们有利的东西,不过大家不要气馁,相信有些人已经看到另一个对象mContentParent,顾名思义,它应该是内容的父类,我们继续往下看 mContentParent = generateLayout(mDecor); 这个MContentParent是通过mDecor创建的。接下来,我们看一下generateLayout()方法:
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
protected ViewGroup generateLayout(DecorView decor) {
...
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
通过对源码的查看,我们知道了mContentParent是DecorView 的一个子View,而contentParent是通过findViewById()这个方法得到的,而这个Id就是 R.id.content ,所以我们找到了页面的另一父容器mContentParent,并获取到了它在布局中的Id。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
这样我们在BaseActivity中就能得到mContentParent对象了。而且它也是一个帧布局,讲到这里应该比较清楚了,我们可以直接将LoadingProgress添加到mContentParent中,并在BaseActivity中进行逻辑控制。
public abstract class BaseActivity extends AppCompatActivity {
private LoadingProgress mProgress;
private List<View> views = new ArrayList<>();
private boolean isLoading = false;
private ViewGroup mContentView;
@Override
public void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
initView();
initData();
initListener();
}
@Override
public void setContentView(int layoutResID) {
super.setContentView(layoutResID);
//在setContentView() 中所设置布局的父容器的ID 是 android.R.id.content
mContentView = (ViewGroup) findViewById(android.R.id.content);
setupLoadView();
}
/**
* 初始化加载动画视图
* 找到布局中的所有一级子view
*/
private void setupLoadView() {
if (mProgress != null)
return;
mProgress = new LoadingProgress(this);
mProgress.setBackgroundResource(R.color.all_bg);
View contentView = mContentView.getChildAt(0);
if (contentView instanceof ViewGroup) {
ViewGroup contentGroup = (ViewGroup) contentView;
for (int i = 1; i < contentGroup.getChildCount(); i++) {
views.add(contentGroup.getChildAt(i));
}
}
int marginTop = getTitleHeight();
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
layoutParams.setMargins(0, marginTop, 0, 0);
mContentView.addView(mProgress, layoutParams);
}
/**
* 开启动画
*/
protected void openLoadAnim() {
if (isLoading || mProgress == null)
return;
mProgress.show();
isLoading = true;
}
/**
* 关闭动画
*/
protected void closeLoadAnim() {
if (!isLoading || mProgress == null)
return;
mProgress.dismiss();
isLoading = false;
}
/**
* 初始化view
* 设置view 部分属性(显示隐藏等)
*/
protected abstract void initView();
/**
* 加载数据
* 在这里调用 openLoadAnim() 方法
*/
protected abstract void initData();
/**
* 初始化监听
*/
protected abstract void initListener();
/**
* 返回title高度,防止加载动画格挡标题
*/
protected abstract int getTitleHeight();
/**
* 父容器获取焦点,禁止子控件自动获取焦点
* 布局中有EditText时,禁止弹出软键盘
*/
protected void containerFocus() {
mContentView.getChildAt(0).setFocusable(true);
}
@Override
protected void onDestroy() {
super.onDestroy();
closeLoadAnim();
mProgress = null;
views.clear();
views = null;
mContentView = null;
}
}
大家可能看到我没有在onCreate中调用initView(),initData(),initListener()方法,而是在onPostCreate方法中调用。原因就留给大家去探索了。