突破父控件的布局限制,让子view从屏幕边缘滑进屏幕的动画效果

         遇到一个需求,就是要求让一个ImageView有一个从屏幕上边缘滑入屏幕中间的动画效果,子view的父控件在屏幕中间,要有从屏幕边缘滑入的效果,则必定要求子view的动画不能限制在其父控件的布局之内,但是android的补间动画(Tween Animation)做不到这个效果,补间动画只能让子view在其父控件内实现动画,而强大的属性动画可以突破父控件布局的限制(这个观点后面测试了,是错误的,也就是说属性动画也不能让子view突破父控件的区域,这里道个歉,想让子view不受约束,可能需要将子view放到更上层的布局里面去,感谢同学的指正.属性动画的优势在于view移动到哪,touch事件就是在哪触发,而补间动画则是表面上移动了view,实际上view的touch事件还是只能在原来最初的位置触发),但是android 的属性动画(Property Animation)是在3.0之后才引入的,在android2.3的系统版本里面无法使用这么强大的动画效果,这就有点捉鸡了。

        在看到别人分析的桌面浮窗(类似某卫士的桌面浮窗控件,点击后可以杀死后台任务)实现原理后,找到了灵感,决定采用浮窗绘制出滑入的动画。

        实现方案就是,先让ImageView在父控件内设置为View.INVISIBLE即不可见的效果,另写一个布局文件,内容就是该ImageView,算是个克隆体吧,之后需要使用这个克隆体来实现动画效果。当绘制的克隆体从屏幕上沿滑入,下滑到预定的位置,就gone掉这个克隆体,让原来的ImageView显示出来即可。当然,为了保证动画效果,还需要确保滑入的体位正确╮(╯▽╰)╭,所以还需要计算出克隆体初始的位置。

        主界面的布局:

        

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:layout_marginTop="50dp"
        android:layout_alignParentTop="true">
        <LinearLayout
            android:id="@+id/linear_ll"
            android:layout_toRightOf="@+id/relative_rl"
            android:layout_toEndOf="@+id/relative_rl"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:layout_marginLeft="10dp">
            <TextView
                android:layout_height="0dp"
                android:layout_width="match_parent"
                android:layout_weight="1"
                android:gravity="center_vertical"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:textStyle="bold"
                android:text="Game of Thrones"/>
            <TextView
                android:layout_height="0dp"
                android:layout_width="match_parent"
                android:layout_weight="1"
                android:gravity="top"
                android:maxLines="3"
                android:lines="3"
                android:ellipsize="end"
                android:textAppearance="?android:attr/textAppearanceSmall"
                android:text="It is an adaptation of A Song of Ice and Fire, George R. R. Martin's series of fantasy novels"/>
        </LinearLayout>
        <RelativeLayout
            android:id="@+id/relative_rl"
            android:layout_marginLeft="10dp"
            android:layout_width="80dp"
            android:layout_height="match_parent">
            <ImageView
                android:id="@+id/cover_imgview"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:scaleType="fitCenter"
                android:adjustViewBounds="true"
                android:src="@drawable/thrones"/>
            <ImageView
                android:id="@+id/arrow_imgview"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_alignParentRight="true"
                android:layout_alignParentBottom="true"
                android:scaleType="fitCenter"
                android:adjustViewBounds="true"
                android:src="@drawable/sword"
                android:visibility="invisible"/>
        </RelativeLayout>
    </RelativeLayout>
</RelativeLayout>


提取出上面id为arrow_imgview这个ImageView出来,写一个单独的布局layout_arrow_window.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <ImageView android:id="@+id/arrow_imgview"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:scaleType="fitCenter"
        android:adjustViewBounds="true"
        android:src="@drawable/sword" >
    </ImageView>
</LinearLayout>

接下来就是关键点了,使用WindowManager实现浮窗效果,跳出主界面,在主界面之上绘制一个window,window里面添加ImageView的克隆体,操作这个window移动,实现从屏幕滑入的效果:

        mWindowMgr = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
        mArrowPopupWindow = (View)LayoutInflater.from(this).inflate(R.layout.layout_arrow_window, null);

        //获取window的LayoutParams,通过修改其参数,改变window在屏幕的位置
        wParams = getDefaultWindowParams();
        mWindowMgr.addView(mArrowPopupWindow, wParams);

其中getDefaultWindowParams()是用于获取window的LayoutParams参数,其中window的级别使用Toast的级别WindowManager.LayoutParams.TYPE_TOAST,要使用这个权限,还需要在manifest.xml里面添加权限<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>,当然在这里,还要计算克隆体的初始位置:

    private WindowManager.LayoutParams getDefaultWindowParams() {
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                0, 0,
                WindowManager.LayoutParams.TYPE_TOAST,
                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                PixelFormat.RGBA_8888);
        params.gravity = Gravity.LEFT | Gravity.TOP;

        //计算ImageView在屏幕中的x值和y值
        int[] location = new int[2];
        mLayout.getLocationInWindow(location);
        int startX = location[0] + mLayout.getWidth() - mArrowImgView.getWidth();

        //这里计算系统状态栏的高度,因为window的LayoutParams里面的y值是从状态栏下沿开始计算的
        Rect rect = new Rect();
        getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
        int statusbarHeight = rect.top;
        MAX_Y = location[1] + mLayout.getHeight() - mArrowImgView.getHeight() - statusbarHeight;

        //克隆体的y值当然从0开始了,最大值就是ImageView的y值
        params.x = startX;
        params.y = 0;

        return params;
    }

然后就是使用一个Handler去一帧一帧绘制了,这里为了让滑入的动画有一个加速效果,使用了一个迭代子,每次绘制过后再自增,让下一次的下滑幅度加大:

    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            switch(msg.what) {
                case PLAY_FALL_DOWN_AMIN: {
                    if(wParams.y >= MAX_Y) {
                        mWindowMgr.removeView(mArrowPopupWindow);
                        removeMessages(PLAY_FALL_DOWN_AMIN);

                        mArrowImgView.setVisibility(View.VISIBLE);
                        mLayout.startAnimation(faderight);

                        iteration = 0;

                        return;
                    }
                    mWindowMgr.updateViewLayout(mArrowPopupWindow, wParams);
                    //为了有一个加速的效果,使用一个迭代子自增
                    wParams.y += iteration++;

                    sendMessageDelayed(obtainMessage(PLAY_FALL_DOWN_AMIN), 20);
                }break;
            }
        }
    };

基本搞定了,下面再贴个完整的代码吧:

public class MainActivity extends Activity {
    private ViewGroup mLayout;
    private ViewGroup mLinearLayout;
    private ImageView mArrowImgView;
    private View mArrowPopupWindow;
    private Animation falldown;
    private Animation faderight;
    WindowManager mWindowMgr;
    WindowManager.LayoutParams wParams;

    public static int MAX_Y = 270;
    private static int iteration = 0;

    public final static int PLAY_FALL_DOWN_AMIN = 0;

    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            switch(msg.what) {
                case PLAY_FALL_DOWN_AMIN: {
                    if(wParams.y >= MAX_Y) {
                        mWindowMgr.removeView(mArrowPopupWindow);
                        removeMessages(PLAY_FALL_DOWN_AMIN);

                        mArrowImgView.setVisibility(View.VISIBLE);
                        mLayout.startAnimation(faderight);

                        iteration = 0;

                        return;
                    }
                    mWindowMgr.updateViewLayout(mArrowPopupWindow, wParams);
                    //I want a acceleration effect
                    wParams.y += iteration++;

                    sendMessageDelayed(obtainMessage(PLAY_FALL_DOWN_AMIN), 20);
                }break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mLayout = (RelativeLayout)findViewById(R.id.relative_rl);
        mLinearLayout = (LinearLayout)findViewById(R.id.linear_ll);
        mArrowImgView = (ImageView)findViewById(R.id.arrow_imgview);

        faderight = AnimationUtils.loadAnimation(this, R.anim.fade_right);

        mWindowMgr = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
        mArrowPopupWindow = (View)LayoutInflater.from(this).inflate(R.layout.layout_arrow_window, null);

        faderight.setAnimationListener(new AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {
            }
            
            @Override
            public void onAnimationRepeat(Animation animation) {
            }
            
            @Override
            public void onAnimationEnd(Animation animation) {
                mLayout.clearAnimation();
                mArrowImgView.setVisibility(View.INVISIBLE);
            }
        });

        mLayout.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View arg0) {
                wParams = getDefaultWindowParams();
                mWindowMgr.addView(mArrowPopupWindow, wParams);

                mHandler.sendEmptyMessage(PLAY_FALL_DOWN_AMIN);
            }
        });

        
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    private WindowManager.LayoutParams getDefaultWindowParams() {
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                0, 0,
                WindowManager.LayoutParams.TYPE_TOAST,
                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                PixelFormat.RGBA_8888);
        params.gravity = Gravity.LEFT | Gravity.TOP;

        int[] location = new int[2];
        mLayout.getLocationInWindow(location);
        int startX = location[0] + mLayout.getWidth() - mArrowImgView.getWidth();

        Rect rect = new Rect();
        getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
        int statusbarHeight = rect.top;
        MAX_Y = location[1] + mLayout.getHeight() - mArrowImgView.getHeight() - statusbarHeight;

        params.x = startX;
        params.y = 0;

        return params;
    }
}

Demo传到了github上:https://github.com/YoungLeeForeverBoy/DemoFallDownAnimation






展开阅读全文

没有更多推荐了,返回首页