簡要介紹
這篇博文主要是自己在借鑒一個從底部滑出布局的屬性動畫過程中,遇到了一點小疑惑,進而去查閱了一點相關資料,總結一下對window、Activity、DecorView、ViewRoot關系的了解,同時也非常感謝點選文章給我帶來的幫助。也是借鑒了一篇關于做從底部滑出的動畫的博文的demo,這裡自己将給出一點自己的了解注釋。
關于View的一點解析
這裡給出的解析實際是留給自己看的,供自己學習,怕以後忘記了,便于檢視。
Activity并不負責視圖控制,它隻是控制生命周期和處理事件,真正控制視圖的是Window。一個Activity包含了一個Window,Window才是真正代表一個視窗,Window 中持有一個 DecorView,而這個DecorView才是 view 的根布局
DecorView是FrameLayout的子類,它可以被認為是Android視圖樹的根節點視圖。DecorView作為頂級View,一般情況下它内部包含一個豎直方向的LinearLayout,在這個LinearLayout裡面有上下兩個部分(具體情況和Android版本及主體有關),上面的是标題欄,下面的是内容欄。在Activity中通過setContentView所設定的布局檔案其實就是被加到内容欄之中的,而内容欄的id是content,在代碼中可以通過ViewGroup content = (ViewGroup)findViewById(R.android.id.content)來得到content對應的layout。
ViewRoot對應 ViewRootImpl類,它是連接配接WindowManagerService和DecorView的紐帶 ,View的三大流程(測量(measure),布局(layout),繪制(draw))均通過ViewRoot來完成。ViewRoot并不屬于View樹的一份子。從源碼實作上來看,它既非View的子類,也非View的父類,但是, 它實作了ViewParent接口,這讓它可以作為View的名義上的父視圖 。RootView繼承了Handler類,可以接收事件并分發,Android的所有觸屏事件、按鍵事件、界面重新整理等事件都是通過ViewRoot進行分發的。ViewRoot可以被了解為“View樹的管理者”——它有一個mView成員變量,它指向的對象和上文中Window和Activity的mDecor指向的對象是同一個對象。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0NXYFhGd192UvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2Lc1zYyIGdOhlW5xWbaZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39zNyADOxMDN2EjNxMDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
demo執行個體
首先demo源碼先貼出來。再這裡寫給出自己相關代碼。
主布局檔案:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.lfq.MainActivity">
<LinearLayout
android:id="@+id/ly_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/bt_1"
android:layout_width="match_parent"
android:layout_height="40dp"
android:onClick="onClick"
android:text="點選彈出" />
</LinearLayout>
幀布局:layout_basepickerview.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="@+id/outmost_container"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:background="@color/bgColor_overlay">
<FrameLayout
android:id="@+id/content_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</FrameLayout>
</FrameLayout>
彈出框布局:layout_bottom.xml
<?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:background="@android:color/white"
android:orientation="vertical">
<TextView
android:id="@+id/tv_content1"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:onClick="onClick1"
android:text="test content 1"
android:textColor="#4a4a4a"
android:textSize="16sp" />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#e0e0e0" />
<TextView
android:id="@+id/tv_content2"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:onClick="onClick2"
android:text="test content 2"
android:textColor="#4a4a4a"
android:textSize="16sp" />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#e0e0e0" />
<TextView
android:id="@+id/tv_content3"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:onClick="onClick3"
android:text="test content 3"
android:textColor="#4a4a4a"
android:textSize="16sp" />
</LinearLayout>
在res下建立anim檔案夾,添加兩個動畫xml檔案,slide_in_bottom.xml、slide_out_bottom.xml分别如下:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="500"
android:fromXDelta="0%"
android:toXDelta="0%"
android:fromYDelta="100%"
android:toYDelta="0%"/>
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="500"
android:fromXDelta="0%"
android:toXDelta="0%"
android:fromYDelta="0%"
android:toYDelta="100%"/>
</set>
MainActivity如下:
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick(View view) {
//當點選事件觸發,先執行個體化一個view,該view是一個幀布局,覆寫在主布局上
final BaseBottomView bottomView = new BaseBottomView(this, R.layout.layout_bottom);
bottomView.findViewById(R.id.tv_content1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bottomView.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(Object o) {
Toast.makeText(MainActivity.this, "跳轉SecondActivity", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
});
//為bottomView中的空間添加點選事件
bottomView.findViewById(R.id.tv_content2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "選擇 content2", Toast.LENGTH_SHORT).show();
bottomView.dismiss();
}
});
bottomView.setCancelable(true);
bottomView.show();
}
public void onClick1(View view) {
// Toast.makeText(this, "onClick1", Toast.LENGTH_SHORT).show();
}
public void onClick2(View view) {
Toast.makeText(this, "onClick2", Toast.LENGTH_SHORT).show();
}
public void onClick3(View view) {
Toast.makeText(this, "onClick3", Toast.LENGTH_SHORT).show();
}
}
動畫控制工具類:
public class AnimateUtil {
private static final int INVALID = -;
public static int getAnimationResource(int gravity, boolean isInAnimation) {
switch (gravity) {
case Gravity.BOTTOM:
return isInAnimation ? R.anim.slide_in_bottom : R.anim.slide_out_bottom;
}
return INVALID;
}
}
取消彈框接口:
public interface OnDismissListener {
void onDismiss(Object o);
}
關鍵的彈框類,這裡重點解釋一下個人了解。
//該view對應一個幀布局,使用者動态的添加到根布局上
public class BaseBottomView {
//設定該幀布局内的子幀布局的布局參數
private final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM
);
private Context context;
protected ViewGroup contentContainer;
private ViewGroup decorView;//activity的根View
private ViewGroup rootView;//附加View 的 根View
private OnDismissListener onDismissListener;
private boolean isDismissing;
private Animation outAnim;
private Animation inAnim;
private int gravity = Gravity.BOTTOM;
public BaseBottomView(Context context, int layoutId) {
this.context = context;
initViews();
init();
//将傳入的layoutId對應的布局渲染到該contentContainer上
LayoutInflater.from(context).inflate(layoutId, contentContainer);
}
protected void initViews() {
LayoutInflater layoutInflater = LayoutInflater.from(context);
//關鍵就是這裡,findViewById(android.R.id.content)
//DecorView作為頂級View,一般情況下它内部包含一個豎直方向的LinearLayout,在這個LinearLayout裡 //面有上下兩個部分上面的是标題欄,下面的是内容欄,也就是findViewById(android.R.id.content)。
decorView = (ViewGroup) ((Activity) context).getWindow().getDecorView().findViewById(android.R.id.content);
//将幀布局添加到内容欄裡 [覆寫了主布局(主布局是相對布局]
rootView = (ViewGroup) layoutInflater.inflate(R.layout.layout_basepickerview, decorView, false);
//設定幀布局參數為全屏
rootView.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
));
//擷取子幀布局,也就是真正的彈框
contentContainer = (ViewGroup) rootView.findViewById(R.id.content_container);
contentContainer.setLayoutParams(params);
}
protected void init() {
inAnim = getInAnimation();
outAnim = getOutAnimation();
}
private void onAttached(View view) {
decorView.addView(view);
contentContainer.startAnimation(inAnim);
}
/**
* 添加這個View到Activity的根視圖
*/
public void show() {
if (isShowing()) {
return;
}
onAttached(rootView);
}
/**
* 檢測該View是不是已經添加到根視圖
*
* @return 如果視圖已經存在該View傳回true
*/
public boolean isShowing() {
View view = decorView.findViewById(R.id.outmost_container);
return view != null;
}
public void dismiss() {
if (isDismissing) {
return;
}
//消失動畫
outAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
decorView.post(new Runnable() {
@Override
public void run() {
//從activity根視圖移除
decorView.removeView(rootView);
isDismissing = false;
if (onDismissListener != null) {
onDismissListener.onDismiss(BaseBottomView.this);
}
}
});
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
contentContainer.startAnimation(outAnim);
isDismissing = true;
}
public Animation getInAnimation() {
int res = AnimateUtil.getAnimationResource(this.gravity, true);
return AnimationUtils.loadAnimation(context, res);}
public Animation getOutAnimation() {
int res = AnimateUtil.getAnimationResource(this.gravity, false);
return AnimationUtils.loadAnimation(context, res);
}
public BaseBottomView setOnDismissListener(OnDismissListener onDismissListener) {
this.onDismissListener = onDismissListener;
dismiss();
return this;
}
public BaseBottomView setCancelable(boolean isCancelable) {
View view = rootView.findViewById(R.id.outmost_container);
if (isCancelable) {
view.setOnTouchListener(onCancelableTouchListener);
} else {
view.setOnTouchListener(null);
}
return this;
}
/**
* Called when the user touch on black overlay in order to dismiss the dialog
*/
private final View.OnTouchListener onCancelableTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
dismiss();
}
return false;
}
};
public View findViewById(int id) {
return contentContainer.findViewById(id);
}
}