3D效果切换 ViewGroup中的子view

效果图先搞一张让大家看看:



灵感来源:https://github.com/zhangke3016/FlipCards

一:介绍

1.首先介绍:
这个自定义的ViewGroup。灵感来源两个,一个是上面Github开源3D动画效果,一个就是在点击项目的退出登陆按钮时,为什么非要弹出一个对话框?其实用一种动画效果,平滑切换至另一个界面,来代替弹出框。(当然可能退出登陆并不是一个恰当的用例,看个人对app设计的理解了)
2.效果如上:
点击退出登陆按钮,3D切换至 包含退出和取消两个按钮的 界面,点击取消-返回,点击退出-提示正在退出登陆。
支持横向纵向3D切换,自动轮播切换。
3.使用:
先看下XML布局文件:
 <com.dup.library.FlipCardViewGroup
        android:id="@+id/group"
        android:layout_width="250dp"
        android:layout_height="100dp"
        android:gravity="center"
        android:padding="20dp"
        app:first_item_index="0">

        <Button
            android:id="@+id/btn_first"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/btn_bg1"
            android:text="退出登录"
            android:textColor="#ffffff" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">

            <Button
                android:id="@+id/btn_second"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="@drawable/btn_bg2"
                android:text="退出"
                android:textColor="#ffffff" />

            <Button
                android:id="@+id/btn_third"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="@drawable/btn_bg3"
                android:text="取消"
                android:textColor="#ffffff" />
        </LinearLayout>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/text_bg3"
            android:gravity="center"
            android:text="退出登录中。。。"
            android:textColor="#fff" />
    </com.dup.library.FlipCardViewGroup>
最外层是自定义的ViewGroup,可以看到效果图中有三个子界面,那么xml中把三个子界面作为ViewGroup的子View就可以了。

二.实现思路

1.自定义ViewGroup,为了可以使用Gravity属性,这里继承RelativeLayout。
2.关键点:如何互相切换?咱们先不考虑3D切换效果。就是单纯控制其中子View的显示与不显示。那好了,在onLayout()控制其子View的布局位置大小(下面会详细介绍)。
3.点击其中一个子View中的button,点击事件中调用ViewGroup中方法,来控制是哪一个子View显示。

三.关键代码

1.重写ViewGroup中onLayout()和onMeasure():
 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();
        if (count == 0) {
            return;
        }
        for (int i = 0; i < count; ++i) {
            View child = getChildAt(i);
            if (child == null || child.getVisibility() == View.GONE) {
                continue;
            }

            if (i == currentIndex) {
                child.layout(0 + getPaddingLeft(), 0 + getPaddingTop(), r - l - getPaddingRight(), b - t - getPaddingBottom());
            } else {
                child.layout(0, 0, 0, 0);
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int count = getChildCount();
        for (int i = 0; i < count; ++i) {
            View childView = getChildAt(i);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
        }
    }
onLayout()方法中:currentIndex就是指的当前要显示的子view的索引。要显示的子view,就使其layout充满父控件(注意padding也要算上)。不显示的子view就使其layout(0,0,0,0)。
2.切换至某一子view公开方法:
 /**
     * 切换至 第index个item
     *
     * @param index 要切换至的item index
     */
    public void changeToItem(final int index) {
        post(new Runnable() {
            @Override
            public void run() {
                int correctIndex = getCorrectIndex(index);
                if (correctIndex == currentIndex) {
                    return;
                }

                currentIndex = correctIndex;

                final FlipCardAnimation animation = new FlipCardAnimation(0, 180, getWidth(), getHeight(), rotateType);
                animation.setDuration(duration);
                animation.setInterpolator(context, interpolator);
                animation.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {

                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {
                        ((FlipCardAnimation) animation).setCanContentChange();
                    }
                });
                animation.setOnContentChangeListener(new FlipCardAnimation.OnContentChangeListener() {
                    @Override
                    public void contentChange() {
                        requestLayout();
                    }
                });
                startAnimation(animation);
            }
        });
    }
首先改变currentIndex值。这里用到了一个开源的3D动画,当旋转至垂直于屏幕(看不到控件时)会回调OnContentChangeListener,回调时我们requestLayout()请求ViewGroup重新执行一遍onLayout()方法。这样就实现了切换效果了。
3.自动切换:
此ViewGroup也是支持自动切换的,下面是开启自动切换方法:
/**
     * 启动自动转动动画
     *
     * @param fromIndex         :开始index
     * @param toIndex           :结束index
     * @param repeatCount:重复次数
     * @param startdelay:开始动画延时
     * @param idle:间隔动画延时
     * @param isReverse:是否反向播放  eg:如果有三个item<br>
     *                          <li>1-1-正向:item播放顺序是:1,2,0,1</li>
     *                          <li>1-1-反向:1,0,2,1</li>
     *                          <li>0-1-正向:0,1</li>
     *                          <li>0-1-反向:0,2,1</li>
     *                          <li>2-1-正向:2,0,1</li>
     *                          <li>2-1-反向:2,1</li>
     */
    public void playAnimation(final int fromIndex, final int toIndex, final int repeatCount, final long startdelay, final long idle, final boolean isReverse) {
        play_startIndex = fromIndex;
        play_endIndex = toIndex;
        play_repeatCount = repeatCount;
        play_startDelay = (int) startdelay;
        play_idle = (int) idle;
        play_isreverse = isReverse;
        post(new Runnable() {
            @Override
            public void run() {
                //1.瞬間到指定开始index
                currentIndex = fromIndex;
                requestLayout();

                mThread = new PlayThread(fromIndex, toIndex, repeatCount, isReverse);
                if (executor != null) {
                    executor.shutdownNow();
                }
                executor = new ScheduledThreadPoolExecutor(1);
                executor.scheduleWithFixedDelay(mThread, startdelay, idle, TimeUnit.MILLISECONDS);
            }
        });

    }
这里用到了ScheduledThreadPoolExecutor线程池,用来间断性地启动 切换线程(PlayThread)。这个线程会计算出下一个显示的子View的Index,并调用上面介绍过的切换至某一子View的方法。
4.切换线程的run()方法
@Override
        public void run() {
            super.run();
            //如果达到重复次数,或不为infinite就停止
            if (hasRepeateCount >= repeateCount && repeateCount != -1) {
                executor.shutdownNow();
                return;
            }
            //正向进行
            if (!isReverse) {
                currentIndex += 1;
                if (currentIndex == getChildCount()) {
                    currentIndex = 0;
                }
                if (currentIndex == toIndex) {
                    hasRepeateCount++;
                }

                changeToItem(currentIndex);
            }
            //反向进行
            else {
                currentIndex -= 1;
                if (currentIndex == -1) {
                    currentIndex = getChildCount() - 1;
                }
                if (currentIndex == toIndex) {
                    hasRepeateCount++;
                }
                changeToItem(currentIndex);
            }
        }
就是一系列的获取下一个Index计算。
5.记得有动画的自定义View要及时停止动画
@Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        clearAnimation();
        if (executor != null) {
            executor.shutdownNow();
        }
    }
6.数据持久化也要做
@Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putInt(BUNDLE_FIRST, firstIndex);
        bundle.putInt(BUNDLE_CURRENT, currentIndex);
        bundle.putParcelable(BUNDLE_DEF, super.onSaveInstanceState());
        return bundle;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            currentIndex = bundle.getInt(BUNDLE_CURRENT);
            firstIndex = bundle.getInt(BUNDLE_FIRST);
            super.onRestoreInstanceState(bundle.getParcelable(BUNDLE_DEF));
        } else {
            super.onRestoreInstanceState(state);
        }
    }
这里关键点就是保存数据分两部分,一部分是View自己的数据,一部分就是咱们自己定义的数据。

四.总结

其中自定义的3D动画还是挺有意思的,当旋转过了90度是需要特殊处理的,否则view会倒过来显示。。这个自定义动画在博文最开始有附Github地址,大家可以去看看。我使用此动画时也做了修改,新增了横向转动的效果。具体代码和使用放到了本人Github上面: 传送门:此项目Github地址链接
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值