Euclid开源源码解析

项目地址https://github.com/Yalantis/Euclid

依赖:CircularReveal
           ListViewAnimations

这里写图片描述

理解这个效果一定要看明白xml布局里每个元素直接的关系,以及每个元素在Z轴上的层次关系。

<!--  灵活的使用相对布局-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/wrapper"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

   <!--  最底层的就是那个Following Toolbar-->
   <FrameLayout
        android:id="@+id/toolbar_list"
        android:layout_width="match_parent"
        android:layout_height="@dimen/height_toolbar"
        android:background="@color/orange">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:fontFamily="sans-serif-bold"
            android:text="@string/title_following"
            android:textColor="@color/black"
            android:textSize="@dimen/text_size_toolbar_title" />

    </FrameLayout>

    <!--  第二层的ListView-->
    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/toolbar_list"
        android:background="@color/gray"
        android:divider="@drawable/list_divider"
        tools:listitem="@layout/list_item" />


    <!--  以下页面就是简介详情的页面了开始默认是隐藏的-->
    <!--  从上到下的Toolbar-->
    <RelativeLayout
        android:id="@+id/toolbar_profile"
        android:layout_width="match_parent"
        android:layout_height="@dimen/height_toolbar"
        android:background="@color/gray"
        android:clickable="true"
        android:orientation="horizontal"
        android:visibility="invisible">

        <ImageView
            android:id="@+id/toolbar_profile_back"
            android:layout_width="@dimen/height_toolbar"
            android:layout_height="match_parent"
            android:layout_centerVertical="true"
            android:layout_gravity="center"
            android:padding="15dp"
            android:src="@drawable/ic_arrow_left" />

        <RelativeLayout
            android:layout_width="200dp"
            android:layout_height="33dp"
            android:layout_centerInParent="true"
            android:background="@drawable/bg_black_oval">

            <TextView
                style="@style/TextViewProfileToolbarTitle"
                android:layout_margin="2dp"
                android:background="@drawable/bg_orange_oval"
                android:text="@string/toolbar_bio"
                android:textColor="@color/black" />

            <TextView
                style="@style/TextViewProfileToolbarTitle"
                android:layout_alignParentRight="true"
                android:text="@string/toolbar_details"
                android:textColor="@color/white" />

        </RelativeLayout>

    </RelativeLayout>


    <!--  简介详情-->
    <LinearLayout
        android:id="@+id/wrapper_profile_details"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="@dimen/height_profile_picture_with_toolbar"
        android:background="@color/white"
        android:clickable="true"
        android:orientation="vertical"
        android:visibility="invisible">

        <TextView
            android:id="@+id/text_view_profile_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:fontFamily="sans-serif-light"
            android:textColor="@color/gray"
            android:textSize="32sp"
            tools:text="SOPHIA" />

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp">

           <TextView
                android:id="@+id/text_view_profile_description"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:fontFamily="sans-serif"
                android:textColor="@color/gray"
                android:textSize="18sp"
                tools:text="@string/lorem_ipsum_long" />

        </ScrollView>

    </LinearLayout>

    <!-- 简介里的悬浮按钮,在RelativeLayout最顶层-->
    <include
        android:id="@+id/button_profile"
        layout="@layout/button_round_msg"
        android:layout_width="@dimen/size_button_message"
        android:layout_height="@dimen/size_button_message"
        android:layout_alignParentRight="true"
        android:layout_marginRight="15dp"
        android:layout_marginTop="@dimen/margin_top_button_message"
        android:visibility="invisible" />

</RelativeLayout>

怎么给ListView添加适配器就不说了,但是里面有一个圆形图(其实不是圆形的,只是因为ImagView上面还被一个View覆盖,但是这个View有个特殊的背景)
看看是什么背景

//好像我稍微改了点东西。。。
private ShapeDrawable buildAvatarCircleOverlay() {
       //获取半径
        int radius = dpToPx(getCircleRadiusDp());
        ShapeDrawable overlay = new ShapeDrawable(new RoundRectShape(null,
                new RectF(
                        sScreenWidth / 2 - radius,
                        sProfileImageHeight / 2 - radius,
                        sScreenWidth / 2 - radius,
                        sProfileImageHeight / 2 - radius),
                //1、2左上角,3、4右上角,5、6右下角,7、8左下角,如果没弧度的话,传入null即可。
                new float[]{radius, radius, radius, radius, radius, radius, radius, radius}));
        overlay.getPaint().setColor(getResources().getColor(R.color.gray));
        overlay.getPaint().setAntiAlias(true);
        return overlay;
    }

RoundRectShape里的第二个参数里的值就是与外部矩形的边距。相当于把一个矩形中间个掏空了。掏空后中间就是透明的了,所以你就可以看到一个圆形的图片。

看重点了ListView的点击事件

     mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
               //正在执行动画状态
                mState = EuclidState.Opening;
                showProfileDetails((Map<String, Object>) parent.getItemAtPosition(position), view);
            }
        });
   private void showProfileDetails(Map<String, Object> item, final View view) {
        //防止出现滚动
        mListView.setEnabled(false);

        //根据view的Top算出相对动画时间
        int profileDetailsAnimationDelay =
         getMaxDelayShowDetailsAnimation() * Math.abs(view.getTop())/ sScreenWidth;

        //一个和当前点击Item元素一样布局的view,
        // 覆盖在当前item元素上,让你以为是当前item元素进行了移动
        addOverlayListItem(item, view);

        //执行Reveal动画和把覆盖在Item元素上的布局进行从当前位置移动到toolbar的Bottom位置
        startRevealAnimation(profileDetailsAnimationDelay);

        //执行简介里的button缩放动画和ToolBar、以及简介内容的位移动画
        animateOpenProfileDetails(profileDetailsAnimationDelay);
    }

我们一个一个函数的看
addOverlayListItem

    private void addOverlayListItem(Map<String, Object> item, View view) {
        if (mOverlayListItemView == null) {
            mOverlayListItemView = 
            //跟ListView里的Item元素 的布局是一样的,这很重要!!
      getLayoutInflater().inflate(R.layout.overlay_list_item, mWrapper, false);
        } else {
            mWrapper.removeView(mOverlayListItemView);
        }

        mOverlayListItemView.findViewById(R.id.view_avatar_overlay).setBackground(sOverlayShape);

        Picasso.with(EuclidActivity.this)
                .load((Integer) item.get(EuclidListAdapter.KEY_AVATAR))
                .resize(sScreenWidth, sProfileImageHeight)
                .centerCrop()
                .placeholder(R.color.blue)
                .into((ImageView) mOverlayListItemView.findViewById(R.id.image_view_reveal_avatar));
//        Picasso.with(EuclidActivity.this)
//                .load((Integer) item.get(EuclidListAdapter.KEY_AVATAR))
//                .resize(sScreenWidth, sProfileImageHeight)
//                .centerCrop()
//                .placeholder(R.color.blue)
//                .into((ImageView) mOverlayListItemView.findViewById(R.id.image_view_avatar));

        ((TextView) mOverlayListItemView.findViewById(R.id.text_view_name)).setText((String) item.get(EuclidListAdapter.KEY_NAME));
        ((TextView) mOverlayListItemView.findViewById(R.id.text_view_description)).setText((String) item.get(EuclidListAdapter.KEY_DESCRIPTION_SHORT));
        setProfileDetailsInfo(item);

//动态的添加一个View到相对布局里,并且把这个View显示在你点击ListView的那个Item一样的位置。
//这样你就感觉是item发生了动画(其实不是)。
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        params.topMargin = view.getTop() + mToolbar.getHeight();
        params.bottomMargin = -(view.getBottom() - mListView.getHeight());
        mWrapper.addView(mOverlayListItemView, params);
        mToolbar.bringToFront();
    }

startRevealAnimation

 private void startRevealAnimation(final int profileDetailsAnimationDelay) {
        mOverlayListItemView.post(new Runnable() {
            @Override
            public void run() {
                getAvatarRevealAnimator().start();
                getAvatarShowAnimator(profileDetailsAnimationDelay).start();
            }
        });
    }

    private SupportAnimator getAvatarRevealAnimator() {

       //很简单的Reveal动画
        final LinearLayout mWrapperListItemReveal = (LinearLayout) mOverlayListItemView.findViewById(R.id.wrapper_list_item_reveal);

//计算最终半径
        int finalRadius = Math.max(mOverlayListItemView.getWidth(), mOverlayListItemView.getHeight());

//重点是SupportAnimator ,如果不懂,看CircularReveal
        final SupportAnimator mRevealAnimator = ViewAnimationUtils.createCircularReveal(
                mWrapperListItemReveal,       //目标View
                sScreenWidth / 2,             //目标View的发生动画的x轴上的点
                sProfileImageHeight / 2,      //目标View的发生动画的y轴上的点
                dpToPx(getCircleRadiusDp()),  //起始半径
                        finalRadius);         //最终的半径
        mRevealAnimator.setDuration(getRevealAnimationDuration());
        mRevealAnimator.addListener(new SupportAnimator.AnimatorListener() {
            @Override
            public void onAnimationStart() {
                mWrapperListItemReveal.setVisibility(View.VISIBLE);
                mOverlayListItemView.setX(0);
            }
            @Override
            public void onAnimationEnd() {
            }

            @Override
            public void onAnimationCancel() {
            }

            @Override
            public void onAnimationRepeat() {
            }
        });
        return mRevealAnimator;
    }

   private Animator getAvatarShowAnimator(int profileDetailsAnimationDelay) {
   //很简单的属性位移动画
   //把覆盖的view从当前的位置移动到ToolBar的Bottom位置
        final Animator mAvatarShowAnimator = ObjectAnimator.ofFloat(mOverlayListItemView, View.Y, mOverlayListItemView.getTop(), mToolbarProfile.getBottom());
        mAvatarShowAnimator.setDuration(profileDetailsAnimationDelay + getAnimationDurationShowProfileDetails());
        mAvatarShowAnimator.setInterpolator(new DecelerateInterpolator());
        return mAvatarShowAnimator;
    }
private void animateOpenProfileDetails(int profileDetailsAnimationDelay) {
        createOpenProfileButtonAnimation();
        getOpenProfileAnimatorSet(profileDetailsAnimationDelay).start();
    }
    private void createOpenProfileButtonAnimation() {
    //给悬浮按钮的缩放动画,注意,不是立即就执行动画
        if (mProfileButtonShowAnimation == null) {
            mProfileButtonShowAnimation = AnimationUtils.loadAnimation(this, R.anim.profile_button_scale);
            mProfileButtonShowAnimation.setDuration(getAnimationDurationShowProfileButton());
            mProfileButtonShowAnimation.setInterpolator(new AccelerateInterpolator());
            mProfileButtonShowAnimation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
                    mButtonProfile.setVisibility(View.VISIBLE);
                }

                @Override
                public void onAnimationEnd(Animation animation) {

                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });
        }
    }

profile_button_scale.xml

<?xml version="1.0" encoding="utf-8"?>
<set android:shareInterpolator="false"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <scale
        android:fromXScale="0.0"
        android:toXScale="1.0"
        android:fromYScale="0.0"
        android:toYScale="1.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:fillAfter="false"/>
</set>
  //包含2个动画,一个是ToolBar的位移向下和详细页位移向上的动画
    private AnimatorSet getOpenProfileAnimatorSet(int profileDetailsAnimationDelay) {
        if (mOpenProfileAnimatorSet == null) {
            List<Animator> profileAnimators = new ArrayList<>();
            profileAnimators.add(getOpenProfileToolbarAnimator());
            profileAnimators.add(getOpenProfileDetailsAnimator());

            mOpenProfileAnimatorSet = new AnimatorSet();
            mOpenProfileAnimatorSet.playTogether(profileAnimators);
            mOpenProfileAnimatorSet.setDuration(getAnimationDurationShowProfileDetails());
        }
        mOpenProfileAnimatorSet.setStartDelay(profileDetailsAnimationDelay);
        mOpenProfileAnimatorSet.setInterpolator(new DecelerateInterpolator());
        return mOpenProfileAnimatorSet;
    }
    private Animator getOpenProfileToolbarAnimator() {
        Animator mOpenProfileToolbarAnimator = ObjectAnimator.ofFloat(mToolbarProfile, View.Y, -mToolbarProfile.getHeight(), 0);
        mOpenProfileToolbarAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                mToolbarProfile.setX(0);
                mToolbarProfile.bringToFront();
                mToolbarProfile.setVisibility(View.VISIBLE);
                mProfileDetails.setX(0);
                mProfileDetails.bringToFront();
                mProfileDetails.setVisibility(View.VISIBLE);

                mButtonProfile.setX(mInitialProfileButtonX);
                mButtonProfile.bringToFront();
            }

            @Override
            public void onAnimationEnd(Animator animation) {
        //ToolBar动画一结束就开始悬浮按钮的缩放动画        mButtonProfile.startAnimation(mProfileButtonShowAnimation);

                mState = EuclidState.Opened;
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        return mOpenProfileToolbarAnimator;
    }

//很简单的位移动画,从屏幕底下移动到指定位置
    private Animator getOpenProfileDetailsAnimator() {
        Animator mOpenProfileDetailsAnimator = ObjectAnimator.ofFloat(mProfileDetails, View.Y,
                getResources().getDisplayMetrics().heightPixels,
                getResources().getDimensionPixelSize(R.dimen.height_profile_picture_with_toolbar));
        return mOpenProfileDetailsAnimator;
    }

看看关闭时的动画

//  一系列的X轴位移动画
    private AnimatorSet getCloseProfileAnimatorSet() {
        if (mCloseProfileAnimatorSet == null) {
            Animator profileToolbarAnimator = ObjectAnimator.ofFloat(mToolbarProfile, View.X,
                    0, mToolbarProfile.getWidth());

            Animator profilePhotoAnimator = ObjectAnimator.ofFloat(mOverlayListItemView, View.X,
                    0, mOverlayListItemView.getWidth());
            profilePhotoAnimator.setStartDelay(getStepDelayHideDetailsAnimation());

            Animator profileButtonAnimator = ObjectAnimator.ofFloat(mButtonProfile, View.X,
                    mInitialProfileButtonX, mOverlayListItemView.getWidth() + mInitialProfileButtonX);
            profileButtonAnimator.setStartDelay(getStepDelayHideDetailsAnimation() * 2);

            Animator profileDetailsAnimator = ObjectAnimator.ofFloat(mProfileDetails, View.X,
                    0, mToolbarProfile.getWidth());
            profileDetailsAnimator.setStartDelay(getStepDelayHideDetailsAnimation() * 2);

            List profileAnimators = new ArrayList<>();
            profileAnimators.add(profileToolbarAnimator);
            profileAnimators.add(profilePhotoAnimator);
            profileAnimators.add(profileButtonAnimator);
            profileAnimators.add(profileDetailsAnimator);

            mCloseProfileAnimatorSet = new AnimatorSet();
            mCloseProfileAnimatorSet.playTogether(profileAnimators);
            mCloseProfileAnimatorSet.setDuration(getAnimationDurationCloseProfileDetails());
            mCloseProfileAnimatorSet.setInterpolator(new AccelerateInterpolator());
            mCloseProfileAnimatorSet.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                    if (mListViewAnimator != null) {
                        mListViewAnimator.reset();
                        mListViewAnimationAdapter.notifyDataSetChanged();
                    }
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    mToolbarProfile.setVisibility(View.INVISIBLE);
                    mButtonProfile.setVisibility(View.INVISIBLE);
                    mProfileDetails.setVisibility(View.INVISIBLE);

                    mListView.setEnabled(true);
                    mListViewAnimator.disableAnimations();

                    mState = EuclidState.Closed;
                }

                @Override
                public void onAnimationCancel(Animator animation) {

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });
        }
        return mCloseProfileAnimatorSet;
    }

ListView里的item位移效果是在listviewanimations里写的。

总结:

xml布局很重要。
点击ListView的item时,先将一个相同的View添加到当前的相对布局,
接着执行4个动画:
1、以相同的ListView里item布局覆盖在当前相对布局里。
2、执行Reveal动画,从当前圆形的半径执行到覆盖view的最大半径。
3、覆盖View的位移动画
4、ToolBar向下位移,详细view向上位移。
5、第3补动画一结束对悬浮按钮进行缩放动画

退出动画:
就是执行一系列的x轴的位移动画。各个之间有个先后顺序。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
七只熊文库CMS ## 介绍 七只熊是类似百度文库,能够实现文档分享、售卖的文库CMS系统。用户上传源文档后,七只熊会自动将文档进行转码成HTML,成功后,将文档HTML返回文库CMS。实现免插件、在线浏览。 ## 快速体验入口 文库前端演示: http://doc.qizhixiong.com 文库管理后台: http://doc.qizhixiong.com/admin.php 七只熊官网:http://www.qizhixiong.com ## 软件架构 七只熊文库系统,由2个部分组成: 七只熊文库CMS: 用于文档内容管理、用户及权限管理、积分系统等。 七只熊转换系统:本系统不开源。用于配合文库CMS实现将office文档转换成HTML,以实现客户端在线浏览。 ## CMS主要功能概述 分类管理、文档管理、文档预览、收费文档悦读页数限制。 文档积分系统。 支付宝在线积分充值。 用户积分策略自由设置。 新闻系统。 论坛 + 文档悬赏系统。 企业名录系统。 大批量文档客户端软件。 全站广告系统。 文档专辑系统。 ## 安装教程 第一步: 下载七只熊文库。访问 http://您的域名/ 将会自动执行安装程序。 第二步: 进入后台 – 系统 – 站点设置,修改“网站链接”即您的站点域名, 域名后必须加上斜杠“/”,否则将导致图片显示失败,转换失败等问题。 第三步: 联系七只熊获取站点appid、appsecret ,并进入后台 – 系统 – 转换设置填写appid、appsecret。 第四步: 上传文档测试转换效果。 #### 伪静态 伪静态规则文件在程序根目录“伪静态实现”里,如遇到问题请自行百度或联系七只熊协助解决。 Apache RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] Nginx if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=$1 last; break;
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值