Gmail的三个片段动画场景的完整的工作样本?(Complete Working Sample of

2019-06-18 00:27发布

TL; DR:我要找的是什么,我会称之为“Gmail的三个片段动画”的情景完整的工作示例 。 具体来说,我们想先从两个片段,就像这样:

在一些UI事件(例如,轻拍在片段B的东西),我们希望:

  • 片段A到屏幕上滑动到左边
  • 片段B滑动到屏幕的左边缘和收缩占用由片段A腾空点
  • 片段C在从屏幕的右侧滑入,并采取增长片段B腾空点

而且,上一回按下按钮,我们希望这样的操作集被逆转。

现在,我已经看到了很多部分实现的; 下面我将回顾他们四个人。 除了是不完整的,他们都有自己的问题。


@Reto迈尔促成这种普遍的回答相同的基本问题,表明你会使用setCustomAnimations()FragmentTransaction 。 对于两片段的场景(例如,你只能看到片段A开始,并希望用动画效果的新片段B来取代它),我完全同意。 然而:

  • 既然你只能指定一个“in”和一个“出”的动画,我看不出你如何处理所有的三个片段场景需要不同的动画
  • 所述<objectAnimator>他示例代码使用以像素为单位的硬连线的位置,并且这似乎是不现实的给定的不同的屏幕尺寸,但setCustomAnimations()需要动画资源,排除在Java中定义这些东西的可能性
  • 我很茫然以对比例的对象动画师如何配合之类的android:layout_weightLinearLayout对基于百分比分配空间
  • 我很茫然,以C片段是如何在一开始就处理( GONEandroid:layout_weight0 ?预动画提升到一个尺度的0点别的?)

@Roman Nurik指出, 你可以动画任何财产 ,包括你自己定义的。 这可以帮助解决硬连线位置的问题,在你自己发明的自定义布局管理器的子类的费用。 这有助于一些,但我仍然雷托的解决方案的其他百思不得其解。


笔者这个引擎收录条目显示了一些诱人的伪代码,基本上说,所有这三个片段将驻留在容器最初,与C片段隐藏在通过一开始就hide()交易操作。 然后,我们show() C和hide()当UI事件发生时,。 不过,我看不出它处理的是b变为大小的事实。 这也依赖于事实,你显然可以将多个片段添加到同一容器中,我不知道是否是在长期的(更不用提它应该打破可靠的行为findFragmentById()但我可以忍受那)。


笔者这篇博客文章指出Gmail未使用setCustomAnimations()所有,而是直接使用对象的动画(“你只需要改变根视图+改变宽度的右视图中的左旁”)。 然而,这仍然是一个两片段溶液AFAICT,和实现显示在像素再次硬导线的尺寸。


我会继续在这个刻苦,所以我可能结业有朝一日这个回答我,但我真的希望有人已经制定了这个动画脚本三个片段解决方案,可以张贴代码(或其链接)。 在Android的动画让我想拉我的头发,和那些你们谁看见我知道这是徒劳的主要努力。

Answer 1:

在上传我的建议github上 (与所有Android版本的工作,虽然鉴于硬件加速强烈推荐这种动画的。对于非硬件加速设备的位图缓存实现应该更适合)

与动画演示视频是这里 (屏幕演员慢帧速率的原因,实际表现也非常快)


用法:

layout = new ThreeLayout(this, 3);
layout.setAnimationDuration(1000);
setContentView(layout);
layout.getLeftView();   //<---inflate FragmentA here
layout.getMiddleView(); //<---inflate FragmentB here
layout.getRightView();  //<---inflate FragmentC here

//Left Animation set
layout.startLeftAnimation();

//Right Animation set
layout.startRightAnimation();

//You can even set interpolators

阐释

创建新的自定义的RelativeLayout(ThreeLayout)和2个自定义动画(MyScalAnimation,MyTranslateAnimation)

ThreeLayout得到左窗格作为PARAM的重量,假设其他可见视图具有weight=1

因此, new ThreeLayout(context,3)创建了3个孩子一个新的观点,并有总屏的1/3左窗格中。 另一种观点占据了所有可用空间。

它在运行时计算的宽度,更安全的实现是,dimentions被计算第一次拉伸()。 而不是在后()

规模和翻译动画实际调整大小和移动视图,而不是伪[规模,移动。 请注意, fillAfter(true)没有任何地方使用。

视图2是right_of视图1

VIEW3是right_of视图2

已经设置这些规则的RelativeLayout采取一切照顾。 动画改变margins (上移动)和[width,height]在刻度

要访问每个孩子(这样你可以用你的片段夸大它,你可以调用

public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

下面是展示了2个动画


阶段1

---屏幕----------!----- OUT ----

[视图1] [_____视图2 _____] [_____ View3_____]

阶段2

--out - !--------屏幕------

[视图1] [视图2] [_____ View3_____]



Answer 2:

OK,这是我自己的解决方案,从电子邮件应用程序AOSP衍生,每@克里斯托弗的在问题的意见建议。

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePane

@ weakwire的解决方案是让人联想到我的,虽然他采用经典的Animation ,而不是动画师,他用RelativeLayout规则来执行定位。 从赏金的角度来看,他可能会获得赏金,除非别人用雨衣的解决方案还没有张贴一个答案。


简而言之,该ThreePaneLayout在该项目是一个LinearLayout子类,设计景观的工作,有三个孩子。 这些儿童的宽度可以在布局XML来设置,通过什么手段所需 - 我显示了使用权,但你可以有按维度资源或任何设置特定的宽度。 第三个孩子 - C片段中的问题 - 应该有一个零宽度。

package com.commonsware.android.anim.threepane;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;

public class ThreePaneLayout extends LinearLayout {
  private static final int ANIM_DURATION=500;
  private View left=null;
  private View middle=null;
  private View right=null;
  private int leftWidth=-1;
  private int middleWidthNormal=-1;

  public ThreePaneLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    initSelf();
  }

  void initSelf() {
    setOrientation(HORIZONTAL);
  }

  @Override
  public void onFinishInflate() {
    super.onFinishInflate();

    left=getChildAt(0);
    middle=getChildAt(1);
    right=getChildAt(2);
  }

  public View getLeftView() {
    return(left);
  }

  public View getMiddleView() {
    return(middle);
  }

  public View getRightView() {
    return(right);
  }

  public void hideLeft() {
    if (leftWidth == -1) {
      leftWidth=left.getWidth();
      middleWidthNormal=middle.getWidth();
      resetWidget(left, leftWidth);
      resetWidget(middle, middleWidthNormal);
      resetWidget(right, middleWidthNormal);
      requestLayout();
    }

    translateWidgets(-1 * leftWidth, left, middle, right);

    ObjectAnimator.ofInt(this, "middleWidth", middleWidthNormal,
                         leftWidth).setDuration(ANIM_DURATION).start();
  }

  public void showLeft() {
    translateWidgets(leftWidth, left, middle, right);

    ObjectAnimator.ofInt(this, "middleWidth", leftWidth,
                         middleWidthNormal).setDuration(ANIM_DURATION)
                  .start();
  }

  public void setMiddleWidth(int value) {
    middle.getLayoutParams().width=value;
    requestLayout();
  }

  private void translateWidgets(int deltaX, View... views) {
    for (final View v : views) {
      v.setLayerType(View.LAYER_TYPE_HARDWARE, null);

      v.animate().translationXBy(deltaX).setDuration(ANIM_DURATION)
       .setListener(new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
           v.setLayerType(View.LAYER_TYPE_NONE, null);
         }
       });
    }
  }

  private void resetWidget(View v, int width) {
    LinearLayout.LayoutParams p=
        (LinearLayout.LayoutParams)v.getLayoutParams();

    p.width=width;
    p.weight=0;
  }
}

然而,在运行时,无论最初如何设置宽度,宽度管理接管ThreePaneLayout使用第一次hideLeft()从表示被称为什么的问题为片段A和B片段B和C切换在术语ThreePaneLayout -它没有以特定片段的关系-三件leftmiddleright 。 那时的你打电话hideLeft()我们记录的大小leftmiddle及归零按3个已使用的任何重量,所以我们完全可以控制大小。 在时间点hideLeft()我们设置的大小right是原来的尺寸middle

动画有两方面:

  • 使用ViewPropertyAnimator执行三种构件到通过的宽度左的平移left ,使用硬件层
  • 使用ObjectAnimator上的自定义伪属性middleWidth的改变middle宽度不管它开始于原宽度left

(这是可能的,它是一个更好的主意,用一个AnimatorSetObjectAnimators所有这些,虽然这个工程现在)

(也可能的是, middleWidth ObjectAnimator否定硬件层的值,因为这需要相当连续无效)

(这是绝对有可能,我仍然有我的动画的理解的差距,而且我喜欢括号语句)

最终的效果是, left滑离屏幕, middle滑动到原来的位置和大小leftright的后面右平移middle

showLeft()简单地逆转过程中,随着动画的相同结构,只是方向相反。

活动采用ThreePaneLayout持有一对ListFragment部件和一个Button 。 在左片段选择一些添加(或更新的内容)的中间片段。 中间片段选择一些设置的标题Button ,再加上执行hideLeft()ThreePaneLayout 。 按BACK,如果我们躲在左侧,将执行showLeft() ; 否则,BACK退出活动。 由于这种不使用FragmentTransactions的影响的动画,我们被卡住管理是“回栈”自己。

项目链接到的上述使用本地片段和本土动画框架。 我有一个使用Android的支持片段反向移植,并在同一个项目的另一个版本NineOldAndroids为动画:

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePaneBC

该反向移植工作正常,在第一代的Kindle Fire,虽然动画是给定的硬件规格和缺乏支持硬件加速有点生涩。 这两种实现在Nexus 7和其他当前一代的片剂似乎光滑。

我当然开放,提供了什么,我在这里做(或@weakwire使用什么)明显的优势,如何改善这种解决思路,或其他解决方案。

再次感谢大家谁作出了贡献!



Answer 3:

我们建立了一个名为PanesLibrary库,解决了这个问题。 它甚至比发生了什么,因为以前提供更加灵活:

  • 每个窗格可以动态调整
  • 它允许任何数目的窗格(不只是2或3)
  • 窗格内片段保留正确的方向变化。

你可以看看这里: https://github.com/Mapsaurus/Android-PanesLibrary

这里有一个演示: http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be

基本上,它可以让你轻松地添加任意数量的动态调整窗格和附加片段的窗格。 希望你觉得它有用! :)



Answer 4:

建立关你链接到(其中一个例子http://android.amberfog.com/?p=758 ),怎么样动画的layout_weight财产? 这样一来,就可以在动画重量3个片段连接在一起的变化,你会得到奖金,他们都很好地滑动起来:

先从一个简单的布局。 因为我们将要动画layout_weight ,我们需要LinearLayout作为3个板根视图:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
    android:id="@+id/panel1"
    android:layout_width="0dip"
    android:layout_weight="1"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel2"
    android:layout_width="0dip"
    android:layout_weight="2"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel3"
    android:layout_width="0dip"
    android:layout_weight="0"
    android:layout_height="match_parent"/>
</LinearLayout>

然后演示类:

public class DemoActivity extends Activity implements View.OnClickListener {
    public static final int ANIM_DURATION = 500;
    private static final Interpolator interpolator = new DecelerateInterpolator();

    boolean isCollapsed = false;

    private Fragment frag1, frag2, frag3;
    private ViewGroup panel1, panel2, panel3;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        panel1 = (ViewGroup) findViewById(R.id.panel1);
        panel2 = (ViewGroup) findViewById(R.id.panel2);
        panel3 = (ViewGroup) findViewById(R.id.panel3);

        frag1 = new ColorFrag(Color.BLUE);
        frag2 = new InfoFrag();
        frag3 = new ColorFrag(Color.RED);

        final FragmentManager fm = getFragmentManager();
        final FragmentTransaction trans = fm.beginTransaction();

        trans.replace(R.id.panel1, frag1);
        trans.replace(R.id.panel2, frag2);
        trans.replace(R.id.panel3, frag3);

        trans.commit();
    }

    @Override
    public void onClick(View view) {
        toggleCollapseState();
    }

    private void toggleCollapseState() {
        //Most of the magic here can be attributed to: http://android.amberfog.com/?p=758

        if (isCollapsed) {
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 0.0f, 1.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 1.0f, 2.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 2.0f, 0.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
        } else {
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 1.0f, 0.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 2.0f, 1.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 0.0f, 2.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
        }
        isCollapsed = !isCollapsed;
    }

    @Override
    public void onBackPressed() {
        //TODO: Very basic stack handling. Would probably want to do something relating to fragments here..
        if(isCollapsed) {
            toggleCollapseState();
        } else {
            super.onBackPressed();
        }
    }

    /*
     * Our magic getters/setters below!
     */

    public float getPanel1Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)     panel1.getLayoutParams();
        return params.weight;
    }

    public void setPanel1Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel1.getLayoutParams();
        params.weight = newWeight;
        panel1.setLayoutParams(params);
    }

    public float getPanel2Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        return params.weight;
    }

    public void setPanel2Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        params.weight = newWeight;
        panel2.setLayoutParams(params);
    }

    public float getPanel3Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        return params.weight;
    }

    public void setPanel3Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        params.weight = newWeight;
        panel3.setLayoutParams(params);
    }


    /**
     * Crappy fragment which displays a toggle button
     */
    public static class InfoFrag extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle     savedInstanceState) {
            LinearLayout layout = new LinearLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            layout.setBackgroundColor(Color.DKGRAY);

            Button b = new Button(getActivity());
            b.setOnClickListener((DemoActivity) getActivity());
            b.setText("Toggle Me!");

            layout.addView(b);

            return layout;
        }
    }

    /**
     * Crappy fragment which just fills the screen with a color
     */
    public static class ColorFrag extends Fragment {
        private int mColor;

        public ColorFrag() {
            mColor = Color.BLUE; //Default
        }

        public ColorFrag(int color) {
            mColor = color;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            FrameLayout layout = new FrameLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

            layout.setBackgroundColor(mColor);
            return layout;
        }
    }
}

另外这个例子没有使用FragmentTransactions实现动画(更确切地说,它的动画片段被连接到的意见),所以你需要自己做所有返回堆栈/片段的交易,但相比让工作动画的努力很好,这似乎并不像一个坏的权衡:)

在行动的它可怕的低分辨率视频: http://youtu.be/Zm517j3bFCo



Answer 5:

这可不是用碎片....它有3个孩子的自定义布局。 当你点击一个消息,你抵消使用3名儿童offsetLeftAndRight()和动画师。

JellyBean ,你可以在“Developper选项”设置启用“显示布局界限”。 当幻灯片动画完成后,你仍然可以看到左边的菜单仍然存在,但中间面板下方。

它类似于西里尔Mottier蝇,在应用程序菜单 ,但与3个单元,而不是2。

Additionnally的ViewPager第三个孩子的是这种行为再次表明: ViewPager通常使用片段(我知道他们没有,但我从来没有见过的其他一个实现Fragment ),因为你无法使用Fragments内另一个Fragment的3个孩子有可能不是碎片....



Answer 6:

我目前正在做这样的事情,但片段B的规模采取的可用空间,而且3窗格可以在同一时间开放,如果有足够的空间。 这里是我的解决方案,到目前为止,但我不知道如果我要坚持下去。 我希望有人会提供出正确的方式回答。

而不是使用的LinearLayout和动画的重量,我使用的RelativeLayout和动画的利润。 我不知道这是最好的方式,因为它需要对requestLayout()的调用,在每次更新。 这是光滑的我所有的设备虽然。

所以,我动画的布局,我没有使用片段交易。 我手动操控后退按钮关闭C片段,如果它是开放的。

FragmentB使用layout_toLeftOf / ToRightOf以保持对齐片段A和C.

当我的应用程序触发一个事件显示片段C,I滑入片段C,以及i滑出式片段A在同一时间。 (2个独立的动画)。 反之,当片段A打开,我关闭C在同一时间。

在肖像模式或更小的屏幕上,我使用稍微不同的布局和在屏幕上滑动片段C。

要使用百分比片段A和C的宽度,我想你将不得不计算它在运行时......(?)

下面是活动的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rootpane"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <!-- FRAGMENT A -->
    <fragment
        android:id="@+id/fragment_A"
        android:layout_width="300dp"
        android:layout_height="fill_parent"
        class="com.xyz.fragA" />

    <!-- FRAGMENT C -->
    <fragment
        android:id="@+id/fragment_C"
        android:layout_width="600dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        class="com.xyz.fragC"/>

    <!-- FRAGMENT B -->
    <fragment
        android:id="@+id/fragment_B"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:layout_marginLeft="0dip"
        android:layout_marginRight="0dip"
        android:layout_toLeftOf="@id/fragment_C"
        android:layout_toRightOf="@id/fragment_A"
        class="com.xyz.fragB" />

</RelativeLayout>

动画,或滑出FragmentC:

private ValueAnimator createFragmentCAnimation(final View fragmentCRootView, boolean slideIn) {

    ValueAnimator anim = null;

    final RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) fragmentCRootView.getLayoutParams();
    if (slideIn) {
        // set the rightMargin so the view is just outside the right edge of the screen.
        lp.rightMargin = -(lp.width);
        // the view's visibility was GONE, make it VISIBLE
        fragmentCRootView.setVisibility(View.VISIBLE);
        anim = ValueAnimator.ofInt(lp.rightMargin, 0);
    } else
        // slide out: animate rightMargin until the view is outside the screen
        anim = ValueAnimator.ofInt(0, -(lp.width));

    anim.setInterpolator(new DecelerateInterpolator(5));
    anim.setDuration(300);
    anim.addUpdateListener(new AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Integer rightMargin = (Integer) animation.getAnimatedValue();
            lp.rightMargin = rightMargin;
            fragmentCRootView.requestLayout();
        }
    });

    if (!slideIn) {
        // if the view was sliding out, set visibility to GONE once the animation is done
        anim.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                fragmentCRootView.setVisibility(View.GONE);
            }
        });
    }
    return anim;
}


文章来源: Complete Working Sample of the Gmail Three-Fragment Animation Scenario?