从底部滑出动画引发的关于View的一点理解

简要介绍

这篇博文主要是自己在借鉴一个从底部滑出布局的属性动画过程中,遇到了一点小疑惑,从而去查阅了一点相关资料,总结一下对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指向的对象是同一个对象。
示图如下

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 = -1;
    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);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值