Android UI绘制之View绘制的工作原理
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android UI绘制之View绘制的工作原理相关的知识,希望对你有一定的参考价值。
参考技术A这是androidUI绘制流程分析的第二篇文章,主要分析界面中View是如何绘制到界面上的具体过程。
ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View的三大流程均是通过 ViewRoot 来完成的。在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对象和 DecorView 建立关联。
measure 过程决定了 View 的宽/高, Measure 完成以后,可以通过 getMeasuredWidth 和 getMeasuredHeight 方法来获取 View 测量后的宽/高,在几乎所有的情况下,它等同于View的最终的宽/高,但是特殊情况除外。 Layout 过程决定了 View 的四个顶点的坐标和实际的宽/高,完成以后,可以通过 getTop、getBottom、getLeft 和 getRight 来拿到View的四个顶点的位置,可以通过 getWidth 和 getHeight 方法拿到View的最终宽/高。 Draw 过程决定了 View 的显示,只有 draw 方法完成后 View 的内容才能呈现在屏幕上。
DecorView 作为顶级 View ,一般情况下,它内部会包含一个竖直方向的 LinearLayout ,在这个 LinearLayout 里面有上下两个部分,上面是标题栏,下面是内容栏。在Activity中,我们通过 setContentView 所设置的布局文件其实就是被加到内容栏中的,而内容栏id为 content 。可以通过下面方法得到 content:ViewGroup content = findViewById(R.android.id.content) 。通过 content.getChildAt(0) 可以得到设置的 view 。 DecorView 其实是一个 FrameLayout , View 层的事件都先经过 DecorView ,然后才传递给我们的 View 。
MeasureSpec 代表一个32位的int值,高2位代表 SpecMode ,低30位代表 SpecSize , SpecMode 是指测量模式,而 SpecSize 是指在某种测量模式下的规格大小。
SpecMode 有三类,如下所示:
UNSPECIFIED
EXACTLY
AT_MOST
LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。
对于顶级View,即DecorView和普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同确定;
对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同决定;
MeasureSpec一旦确定,onMeasure就可以确定View的测量宽/高。
小结一下
当子 View 的宽高采用 wrap_content 时,不管父容器的模式是精确模式还是最大模式,子 View 的模式总是最大模式+父容器的剩余空间。
View 的工作流程主要是指 measure 、 layout 、 draw 三大流程,即测量、布局、绘制。其中 measure 确定 View 的测量宽/高, layout 确定 view 的最终宽/高和四个顶点的位置,而 draw 则将 View 绘制在屏幕上。
measure 过程要分情况,如果只是一个原始的 view ,则通过 measure 方法就完成了其测量过程,如果是一个 ViewGroup ,除了完成自己的测量过程外,还会遍历调用所有子元素的 measure 方法,各个子元素再递归去执行这个流程。
如果是一个原始的 View,那么通过 measure 方法就完成了测量过程,在 measure 方法中会去调用 View 的 onMeasure 方法,View 类里面定义了 onMeasure 方法的默认实现:
先看一下 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 方法的源码:
可以看到, getMinimumWidth 方法获取的是 Drawable 的原始宽度。如果存在原始宽度(即满足 intrinsicWidth > 0),那么直接返回原始宽度即可;如果不存在原始宽度(即不满足 intrinsicWidth > 0),那么就返回 0。
接着看最重要的 getDefaultSize 方法:
如果 specMode 为 MeasureSpec.UNSPECIFIED 即未指定模式,那么返回由方法参数传递过来的尺寸作为 View 的测量宽度和高度;
如果 specMode 不是 MeasureSpec.UNSPECIFIED 即是最大模式或者精确模式,那么返回从 measureSpec 中取出的 specSize 作为 View 测量后的宽度和高度。
看一下刚才的表格:
当 specMode 为 EXACTLY 或者 AT_MOST 时,View 的布局参数为 wrap_content 或者 match_parent 时,给 View 的 specSize 都是 parentSize 。这会比建议的最小宽高要大。这是不符合我们的预期的。因为我们给 View 设置 wrap_content 是希望View的大小刚好可以包裹它的内容。
因此:
如果是一个 ViewGroup,除了完成自己的 measure 过程以外,还会遍历去调用所有子元素的 measure 方法,各个子元素再递归去执行 measure 过程。
ViewGroup 并没有重写 View 的 onMeasure 方法,但是它提供了 measureChildren、measureChild、measureChildWithMargins 这几个方法专门用于测量子元素。
如果是 View 的话,那么在它的 layout 方法中就确定了自身的位置(具体来说是通过 setFrame 方法来设定 View 的四个顶点的位置,即初始化 mLeft , mRight , mTop , mBottom 这四个值), layout 过程就结束了。
如果是 ViewGroup 的话,那么在它的 layout 方法中只是确定了 ViewGroup 自身的位置,要确定子元素的位置,就需要重写 onLayout 方法;在 onLayout 方法中,会调用子元素的 layout 方法,子元素在它的 layout 方法中确定自己的位置,这样一层一层地传递下去完成整个 View 树的 layout 过程。
layout 方法的作用是确定 View 本身的位置,即设定 View 的四个顶点的位置,这样就确定了 View 在父容器中的位置;
onLayout 方法的作用是父容器确定子元素的位置,这个方法在 View 中是空实现,因为 View 没有子元素了,在 ViewGroup 中则进行抽象化,它的子类必须实现这个方法。
1.绘制背景( background.draw(canvas); );
2.绘制自己( onDraw );
3.绘制 children( dispatchDraw(canvas) );
4.绘制装饰( onDrawScrollBars )。
dispatchDraw 方法的调用是在 onDraw 方法之后,也就是说,总是先绘制自己再绘制子 View 。
对于 View 类来说, dispatchDraw 方法是空实现的,对于 ViewGroup 类来说, dispatchDraw 方法是有具体实现的。
通过 dispatchDraw 来传递的。 dispatchDraw 会遍历调用子元素的 draw 方法,如此 draw 事件就一层一层传递了下去。dispatchDraw 在 View 类中是空实现的,在 ViewGroup 类中是真正实现的。
如果一个 View 不需要绘制任何内容,那么就设置这个标记为 true,系统会进行进一步的优化。
当创建的自定义控件继承于 ViewGroup 并且不具备绘制功能时,就可以开启这个标记,便于系统进行后续的优化;当明确知道一个 ViewGroup 需要通过 onDraw 绘制内容时,需要关闭这个标记。
参考:《Android开发艺术探索》
Android进阶之绘制-自定义View完全掌握
Android的UI设计可以说是决定一个app质量的关键因素,因为人们在使用app的时候,最先映入眼帘的就是app的界面了,一个美观、充实的界面能够给用户带来非常好的体验,会在用户心中留下好的印象。
对于UI设计,Android原生的控件加上一些开源库的使用,已经能够满足大部分的UI需求,但是,某些比较新颖、花哨的控件效果,我们只能通过自定义View来实现,那么,从该篇博客开始,我将记录关于Android自定义View的学习内容,并将其整理呈现给大家。
我们来实现一个优酷菜单案例,在案例中会涉及到很多方面的知识。
案例效果如下:
对activity_main.xml文件进行修改。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context="com.itcast.youkumenu.MainActivity">
<RelativeLayout
android:id="@+id/level1"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/level1">
</RelativeLayout>
<RelativeLayout
android:id="@+id/level2"
android:layout_width="180dp"
android:layout_height="90dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/level2">
</RelativeLayout>
<RelativeLayout
android:id="@+id/level3"
android:layout_width="280dp"
android:layout_height="140dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/level3">
</RelativeLayout>
</RelativeLayout>
预览一下效果。
貌似效果已经出来了,但是请注意了,我这样布局的话能点到每个圆环吗?看上面的图片,我只能点到蓝色线条框住的矩形,这是因为小圆环被大圆环覆盖了,我们重新修改一下布局代码。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context="com.itcast.youkumenu.MainActivity">
<RelativeLayout
android:id="@+id/level3"
android:layout_width="280dp"
android:layout_height="140dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/level3">
</RelativeLayout>
<RelativeLayout
android:id="@+id/level2"
android:layout_width="180dp"
android:layout_height="90dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/level2">
</RelativeLayout>
<RelativeLayout
android:id="@+id/level1"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/level1">
</RelativeLayout>
</RelativeLayout>
此时预览一下。
我已经能正确点击到每一个圆环,而我的代码只是将三个相对布局调换了一下位置,既然大圆环会覆盖到小圆环,那我们直接把大圆环放到最上面,这样就不会出现覆盖问题了。
这是我们需要注意的一个点。接下来贴出完整的布局代码。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context="com.itcast.youkumenu.MainActivity">
<RelativeLayout
android:id="@+id/level3"
android:layout_width="280dp"
android:layout_height="140dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/level3">
<ImageView
android:id="@+id/channel1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:src="@drawable/channel1" />
<ImageView
android:id="@+id/channel2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/channel1"
android:layout_marginBottom="8dp"
android:layout_marginLeft="33dp"
android:src="@drawable/channel2" />
<ImageView
android:id="@+id/channel3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/channel2"
android:layout_marginBottom="8dp"
android:layout_marginLeft="63dp"
android:src="@drawable/channel3" />
<ImageView
android:id="@+id/channel4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="8dp"
android:src="@drawable/channel4" />
<ImageView
android:id="@+id/channel7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="8dp"
android:layout_marginRight="8dp"
android:src="@drawable/channel7" />
<ImageView
android:id="@+id/channel6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/channel7"
android:layout_alignParentRight="true"
android:layout_marginBottom="8dp"
android:layout_marginRight="33dp"
android:src="@drawable/channel6" />
<ImageView
android:id="@+id/channel5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/channel6"
android:layout_alignParentRight="true"
android:layout_marginBottom="8dp"
android:layout_marginRight="63dp"
android:src="@drawable/channel5" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/level2"
android:layout_width="180dp"
android:layout_height="90dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/level2">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:src="@drawable/icon_search" />
<ImageView
android:id="@+id/icon_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="8dp"
android:src="@drawable/icon_menu" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="8dp"
android:layout_marginRight="8dp"
android:src="@drawable/icon_myyouku" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/level1"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/level1">
<ImageView
android:id="@+id/icon_home"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/icon_home" />
</RelativeLayout>
</RelativeLayout>
这样布局我们就完成了,接下来我们要实现功能了。
我们可以把功能分为两层,我们先完成中间圆环菜单键控制最外层圆环旋转的动画。然后再完成最里层圆环home键控制中间圆环旋转的动画。
修改MainActivity的代码。
package com.itcast.youkumenu;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity
private RelativeLayout level1;
private RelativeLayout level2;
private RelativeLayout level3;
private ImageView icon_menu;
private ImageView icon_home;
/**
* 是否显示最外层圆环
* true:显示
* false:隐藏
*/
private boolean isShowLevel3 = true;
/**
* 是否显示中间圆环
* true:显示
* false:隐藏
*/
private boolean isShowLevel2 = true;
/**
* 是否显示最里层圆环
* true:显示
* false:隐藏
*/
private boolean isShowLevel1 = true;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
private void initView()
level3 = (RelativeLayout) findViewById(R.id.level3);
icon_menu = (ImageView) findViewById(R.id.icon_menu);
level2 = (RelativeLayout) findViewById(R.id.level2);
icon_home = (ImageView) findViewById(R.id.icon_home);
level1 = (RelativeLayout) findViewById(R.id.level1);
MyOnClickListener myOnClickListener = new MyOnClickListener();
//设置点击事件
icon_home.setOnClickListener(myOnClickListener);
icon_menu.setOnClickListener(myOnClickListener);
class MyOnClickListener implements View.OnClickListener
@Override
public void onClick(View v)
switch (v.getId())
case R.id.icon_home://home键
//如果最外层菜单和中间菜单都是显示的,就都设置为隐藏
if(isShowLevel2)
//隐藏中间菜单
isShowLevel2 = false;
Tools.hideView(level2);
if(isShowLevel3)
//隐藏最外层菜单
isShowLevel3 = false;
Tools.hideView(level3,200);
else
//显示中间菜单
isShowLevel2 = true;
Tools.showView(level2);
//如果都是隐藏的,就仅显示中间菜单
break;
case R.id.icon_menu://菜单键
if(isShowLevel3)
//隐藏
isShowLevel3 = false;
Tools.hideView(level3);
else
//显示
isShowLevel3 = true;
Tools.showView(level3);
break;
在显示和隐藏布局的时候,我抽出了一个工具类用于实现旋转动画,工具类代码如下。
package com.itcast.youkumenu;
import android.view.View;
import android.view.animation.RotateAnimation;
import android.widget.RelativeLayout;
/**
* 显示和隐藏指定的控件
*/
class Tools
public static void hideView(View view)
hideView(view,0);
public static void showView(View view)
RotateAnimation ra = new RotateAnimation(180, 360, view.getWidth() / 2, view.getHeight());
ra.setDuration(500);//设置动画播放持续时间
ra.setFillAfter(true);//动画停留在播放完成的状态
view.startAnimation(ra);
public static void hideView(View view, int startOffset)
RotateAnimation ra = new RotateAnimation(0, 180, view.getWidth() / 2, view.getHeight());
ra.setDuration(500);//设置动画播放持续时间
ra.setFillAfter(true);//动画停留在播放完成的状态
ra.setStartOffset(startOffset);//设置动画延迟时间
view.startAnimation(ra);
运行项目,效果如下。
现在我们要实现一下点击menu键也能隐藏圆环。要想实现这个效果,就得对手机按钮进行控制。
重新修改MainActivity的代码。
package com.itcast.youkumenu;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity
private RelativeLayout level1;
private RelativeLayout level2;
private RelativeLayout level3;
private ImageView icon_menu;
private ImageView icon_home;
/**
* 是否显示最外层圆环
* true:显示
* false:隐藏
*/
private boolean isShowLevel3 = true;
/**
* 是否显示中间圆环
* true:显示
* false:隐藏
*/
private boolean isShowLevel2 = true;
/**
* 是否显示最里层圆环
* true:显示
* false:隐藏
*/
private boolean isShowLevel1 = true;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
private void initView()
level3 = (RelativeLayout) findViewById(R.id.level3);
icon_menu = (ImageView) findViewById(R.id.icon_menu);
level2 = (RelativeLayout) findViewById(R.id.level2);
icon_home = (ImageView) findViewById(R.id.icon_home);
level1 = (RelativeLayout) findViewById(R.id.level1);
MyOnClickListener myOnClickListener = new MyOnClickListener();
//设置点击事件
icon_home.setOnClickListener(myOnClickListener);
icon_menu.setOnClickListener(myOnClickListener);
class MyOnClickListener implements View.OnClickListener
@Override
public void onClick(View v)
switch (v.getId())
case R.id.icon_home://home键
//如果最外层菜单和中间菜单都是显示的,就都设置为隐藏
if(isShowLevel2)
//隐藏中间菜单
isShowLevel2 = false;
Tools.hideView(level2);
if(isShowLevel3)
//隐藏最外层菜单
isShowLevel3 = false;
Tools.hideView(level3,200);
else
//显示中间菜单
isShowLevel2 = true;
Tools.showView(level2);
//如果都是隐藏的,就仅显示中间菜单
break;
case R.id.icon_menu://菜单键
if(isShowLevel3)
//隐藏
isShowLevel3 = false;
Tools.hideView(level3);
else
//显示
isShowLevel3 = true;
Tools.showView(level3);
break;
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
if (keyCode == KeyEvent.KEYCODE_MENU)
//如果三个圆环是显示的,就全部隐藏
if (isShowLevel1)
isShowLevel1 = false;
Tools.hideView(level1);
if(isShowLevel2)
//隐藏中间圆环
isShowLevel2 = false;
Tools.hideView(level2,200);
if (isShowLevel3)
//隐藏最外层圆环
isShowLevel3 = false;
Tools.hideView(level3,400);
else
//如果最里层圆环和中间圆环是隐藏的,就显示
//显示最里层圆环
isShowLevel1 = true;
Tools.showView(level1);
//显示中间圆环
isShowLevel2 = true;
Tools.showView(level2,200);
return true;
return super.onKeyDown(keyCode, event);
工具类代码。
package com.itcast.youkumenu;
import android.view.View;
import android.view.animation.RotateAnimation;
import android.widget.RelativeLayout;
/**
* 显示和隐藏指定的控件
*/
class Tools
public static void hideView(View view)
hideView(view,0);
public static void showView(View view)
showView(view,0);
public static void hideView(View view, int startOffset)
RotateAnimation ra = new RotateAnimation(0, 180, view.getWidth() / 2, view.getHeight());
ra.setDuration(500);//设置动画播放持续时间
ra.setFillAfter(true);//动画停留在播放完成的状态
ra.setStartOffset(startOffset);//设置动画延迟时间
view.startAnimation(ra);
public static void showView(View view, int startOffset)
RotateAnimation ra = new RotateAnimation(180, 360, view.getWidth() / 2, view.getHeight());
ra.setDuration(500);//设置动画播放持续时间
ra.setFillAfter(true);//动画停留在播放完成的状态
ra.setStartOffset(startOffset);
view.startAnimation(ra);
运行项目,效果如下。
这样就实现了再点击手机的menu键时圆环旋转消失,但是这样就产生了一个bug,不知道观察了上面的动图大家发现bug没有,当我按menu键隐藏圆环时,我再去点击圆环的位置,圆环还是旋转出来了,按道理我们的圆环消失后,就不能被我们点击出来了吧。这里就涉及到了普通动画和属性动画的区别了。当然,解决办法有很多,我这里介绍两种。
第一种,给每个孩子设置不可以点击。
那很多人就有点子了,可以在Tools类的hideView()方法中添加view.setEnabled(false);
,然后在showView()方法中添加view.setEnabled(true);
,有些人以为这样就能够解决bug了。其实以为这样可以解决问题的人,他就不了解View和ViewGroup的区别,View是不能够对孩子进行操作的,而我们在方法中将传递过来的布局转换为了View,它原先的某些属性就丢失了。其实,对view参数设置了不可点击的话,相对布局确实变得无法被点击了,但是,它的孩子还是可以被点击的。那应该怎么办呢?我们把Tools类中的四个方法的View参数全部改为ViewGroup,然后对ViewGroup的孩子进行禁止点击的操作。具体代码如下。
package com.itcast.youkumenu;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.RotateAnimation;
import android.widget.RelativeLayout;
/**
* 显示和隐藏指定的控件
*/
class Tools
public static void hideView(ViewGroup view)
hideView(view,0);
public static void showView(ViewGroup view)
showView(view,0);
public static void hideView(ViewGroup view, int startOffset)
RotateAnimation ra = new RotateAnimation(0, 180, view.getWidth() / 2, view.getHeight());
ra.setDuration(500);//设置动画播放持续时间
ra.setFillAfter(true);//动画停留在播放完成的状态
ra.setStartOffset(startOffset);//设置动画延迟时间
view.startAnimation(ra);
for(int i = 0;i < view.getChildCount();i++)
View childView = view.getChildAt(i);
//设置不可以点击
childView.setEnabled(false);
public static void showView(ViewGroup view, int startOffset)
RotateAnimation ra = new RotateAnimation(180, 360, view.getWidth() / 2, view.getHeight());
ra.setDuration(500);//设置动画播放持续时间
ra.setFillAfter(true);//动画停留在播放完成的状态
ra.setStartOffset(startOffset);
view.startAnimation(ra);
for(int i = 0;i < view.getChildCount();i++)
View childView = view.getChildAt(i);
//设置不可以点击
childView.setEnabled(true);
这时你再运行项目,然后点击menu键隐藏圆环后,不管你怎么点击,圆环都不会再出来了。
第二种方法,前面也说到了,我们可以通过属性动画解决该bug。
属性动画和普通动画的区别在于,普通动画只有视觉效果,而控件不会改变它的位置;属性动画不仅有动画效果,而且控件会随着动画而改变位置。可以想象,使用属性动画来旋转的话,当动画执行完毕时,布局旋转180度,此时控件都会旋转到屏幕的下方,这样,用户就点击不到控件从而也就不能触发点击事件了。
修改工具类代码。
package com.itcast.youkumenu;
import android.animation.ObjectAnimator;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.RotateAnimation;
import android.widget.RelativeLayout;
/**
* 显示和隐藏指定的控件
*/
class Tools
public static void hideView(ViewGroup view)
hideView(view, 0);
public static void showView(ViewGroup view)
showView(view, 0);
public static void hideView(ViewGroup view, int startOffset)
// RotateAnimation ra = new RotateAnimation(0, 180, view.getWidth() / 2, view.getHeight());
// ra.setDuration(500);//设置动画播放持续时间
// ra.setFillAfter(true);//动画停留在播放完成的状态
// ra.setStartOffset(startOffset);//设置动画延迟时间
// view.startAnimation(ra);
//
// for(int i = 0;i < view.getChildCount();i++)
// View childView = view.getChildAt(i);
// //设置不可以点击
// childView.setEnabled(false);
//
//改为属性动画
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotation", 0, 180);
animator.setDuration(500);
animator.setStartDelay(startOffset);
animator.start();
view.setPivotX(view.getWidth() / 2);
view.setPivotY(view.getHeight());
public static void showView(ViewGroup view, int startOffset)
// RotateAnimation ra = new RotateAnimation(180, 360, view.getWidth() / 2, view.getHeight());
// ra.setDuration(500);//设置动画播放持续时间
// ra.setFillAfter(true);//动画停留在播放完成的状态
// ra.setStartOffset(startOffset);
// view.startAnimation(ra);
//
// for (int i = 0; i < view.getChildCount(); i++)
// View childView = view.getChildAt(i);
// //设置不可以点击
// childView.setEnabled(true);
//
//改为属性动画
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotation", 180, 360);
animator.setDuration(500);
animator.setStartDelay(startOffset);
animator.start();
view.setPivotX(view.getWidth() / 2);
view.setPivotY(view.getHeight());
运行项目,效果和原来相同,但是bug却已经解决了,大家可以自己试一试,原理我也已经说过了。对于动画,Android中分了三个部分,补间动画、帧动画、属性动画,动画的话,我也会在今后的博客中专门讲解一下。那么今天的内容就到这里了。
以上是关于Android UI绘制之View绘制的工作原理的主要内容,如果未能解决你的问题,请参考以下文章
Android UI绘制原理——ActivityWindowViewRootImpl基本关系
Android UI绘制原理——ActivityWindowViewRootImpl基本关系
Android UI绘制原理——ActivityWindowViewRootImpl基本关系