TL; DR:我要找的是什么,我会称之为“Gmail的三个片段动画”的情景完整的工作示例 。 具体来说,我们想先从两个片段,就像这样:
在一些UI事件(例如,轻拍在片段B的东西),我们希望:
- 片段A到屏幕上滑动到左边
- 片段B滑动到屏幕的左边缘和收缩占用由片段A腾空点
- 片段C在从屏幕的右侧滑入,并采取增长片段B腾空点
而且,上一回按下按钮,我们希望这样的操作集被逆转。
现在,我已经看到了很多部分实现的; 下面我将回顾他们四个人。 除了是不完整的,他们都有自己的问题。
@Reto迈尔促成这种普遍的回答相同的基本问题,表明你会使用setCustomAnimations()
与FragmentTransaction
。 对于两片段的场景(例如,你只能看到片段A开始,并希望用动画效果的新片段B来取代它),我完全同意。 然而:
- 既然你只能指定一个“in”和一个“出”的动画,我看不出你如何处理所有的三个片段场景需要不同的动画
- 所述
<objectAnimator>
他示例代码使用以像素为单位的硬连线的位置,并且这似乎是不现实的给定的不同的屏幕尺寸,但setCustomAnimations()
需要动画资源,排除在Java中定义这些东西的可能性 - 我很茫然以对比例的对象动画师如何配合之类的
android:layout_weight
在LinearLayout
对基于百分比分配空间 - 我很茫然,以C片段是如何在一开始就处理(
GONE
? android:layout_weight
的0
?预动画提升到一个尺度的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
-它没有以特定片段的关系-三件left
, middle
和right
。 那时的你打电话hideLeft()
我们记录的大小left
和middle
及归零按3个已使用的任何重量,所以我们完全可以控制大小。 在时间点hideLeft()
我们设置的大小right
是原来的尺寸middle
。
动画有两方面:
- 使用
ViewPropertyAnimator
执行三种构件到通过的宽度左的平移left
,使用硬件层 - 使用
ObjectAnimator
上的自定义伪属性middleWidth
的改变middle
宽度不管它开始于原宽度left
(这是可能的,它是一个更好的主意,用一个AnimatorSet
和ObjectAnimators
所有这些,虽然这个工程现在)
(也可能的是, middleWidth
ObjectAnimator
否定硬件层的值,因为这需要相当连续无效)
(这是绝对有可能,我仍然有我的动画的理解的差距,而且我喜欢括号语句)
最终的效果是, left
滑离屏幕, middle
滑动到原来的位置和大小left
和right
的后面右平移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?