編輯:關於Android編程
在 Android 5.0上 google 官方給我提供了不少好看方便使用的轉場動畫
原生提供的普通轉場動畫
- fade 漸隱漸現
- slid 各元素先後滑動進入
- Explode 分裂成連個部分以前進入
分享元素的轉場動畫
- changeBound 這個是最長使用的 改變View 大小和位置
- changeClipBounds 改變 Clip 邊界的大小
- changeImageTransform 改變ImageView 的大小 和 martix
- ChangeTransform 改變普通的 View 一些Scalex 值
- ChangeScroll 改變滑動位置
以上都是原生的. 但是面對一些復雜的轉場動畫,google 提供的這幾個還是不夠, 很多時候都需要自己定義轉場動畫.
例如下轉場動畫, 使用原生的這些動畫很難實現:
一開始我想使用 官方元素的slid或Explode來實現, 但是基本都難以實現
後面只能自己寫一個轉場動畫,轉場動畫也簡單.
只需要繼承 Visibility
或 Transition
其中 Visibility
是繼承自Transition
的
如果轉場動畫只是某個 View 出現或消失, 那麼可以考慮繼承 Visibility
如果是累 ShareElem這樣的轉場動畫, 那麼就需要繼承 Transition
回到重點, 我們需要實現 , 第一個問題:
頂部 title bar 和底部輸入框的 進入 返回動畫
繼承Visibility
有4個方法需要我們重寫:
1. public void captureStartValues(TransitionValues transitionValues)
這裡保存計算動畫初始狀態的一個屬性值
2. public void captureEndValues(TransitionValues transitionValues)
這裡保存計算動畫結束狀態的一個屬性值
3. public Animator onAppear(ViewGroup sceneRoot, final View view, TransitionValues startValues, TransitionValues endValues)
如果是進入動畫 即顯示某個 View 則會執行這個方法
4. public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues, TransitionValues endValues)
如果是退出 , 即不在顯示某個 VIew 則會執行這個方法
我們可以看到頂部 title bar 和 底部輸入框 輸入框的動畫其實非常簡單, 僅僅只有TranslationY
這一個動畫.
我們只需要在captureStartValues
,captureEndValues
兩個方法中分別計算和保存開始和解釋位置需要的位移
然後在 onAppear
和onDisappear
方法創建動畫即可
具體請查看代碼:
public class CommentEnterTransition extends Visibility {
private static final String TAG = "CommentEnterTransition";
private static final String PROPNAME_BOTTOM_BOX_TRANSITION_Y = "custom_bottom_box_enter_transition:change_transY:transitionY";
private static final String PROPNAME_TOP_BAR_TRANSITION_Y = "custom_top_bar_transition:change_transY:transitionY";
private View mBottomView;
private View mTopBarView;
private Context mContext;
public CommentEnterTransition(Context context, View topBarView, View bottomView) {
mBottomView = bottomView;
mTopBarView = topBarView;
mContext = context;
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
super.captureStartValues(transitionValues);
mBottomView.measure(0, 0);
int transY = mBottomView.getMeasuredHeight();
// 保存 計算初始值
transitionValues.values.put(PROPNAME_BOTTOM_BOX_TRANSITION_Y, transY);
transitionValues.values.put(PROPNAME_TOP_BAR_TRANSITION_Y, -mContext.getResources().getDimensionPixelOffset(R.dimen.top_bar_height));
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
super.captureEndValues(transitionValues);
// 保存計算結束值
transitionValues.values.put(PROPNAME_BOTTOM_BOX_TRANSITION_Y, 0);
transitionValues.values.put(PROPNAME_TOP_BAR_TRANSITION_Y, 0);
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
return super.createAnimator(sceneRoot, startValues, endValues);
}
@Override
public Animator onAppear(ViewGroup sceneRoot, final View view,
TransitionValues startValues, TransitionValues endValues) {
if (null == startValues || null == endValues) {
return null;
}
// 這裡去除 之前 存儲的 初始值 和 結束值, 然後執行東湖
if (view == mBottomView) {
int startTransY = (int) startValues.values.get(PROPNAME_BOTTOM_BOX_TRANSITION_Y);
int endTransY = (int) endValues.values.get(PROPNAME_BOTTOM_BOX_TRANSITION_Y);
if (startTransY != endTransY) {
ValueAnimator animator = ValueAnimator.ofInt(startTransY, endTransY);
// 注意這裡不能使用 屬性動畫, 使用 ValueAnimator 然後在更新 View 的對應屬性
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Object value = animation.getAnimatedValue();
if (null != value) {
view.setTranslationY((Integer) value);
}
}
});
return animator;
}
} else if (view == mTopBarView) {
int startTransY = (int) startValues.values.get(PROPNAME_TOP_BAR_TRANSITION_Y);
int endTransY = (int) endValues.values.get(PROPNAME_TOP_BAR_TRANSITION_Y);
if (startTransY != endTransY) {
ValueAnimator animator = ValueAnimator.ofInt(startTransY, endTransY);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Object value = animation.getAnimatedValue();
if (null != value) {
view.setTranslationY((Integer) value);
}
}
});
return animator;
}
}
return null;
}
@Override
public Animator onDisappear(ViewGroup sceneRoot, final View view,
TransitionValues startValues, TransitionValues endValues) {
if (null == startValues || null == endValues) {
return null;
}
// 這裡執行 返回動畫, 這裡金 初始值 和技術值 對調了,這樣動畫, 就就和原來動畫想反了
if (view == mBottomView) {
int startTransY = (int) endValues.values.get(PROPNAME_BOTTOM_BOX_TRANSITION_Y);
int endTransY = (int) startValues.values.get(PROPNAME_BOTTOM_BOX_TRANSITION_Y);
if (startTransY != endTransY) {
ValueAnimator animator = ValueAnimator.ofInt(startTransY, endTransY);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Object value = animation.getAnimatedValue();
if (null != value) {
view.setTranslationY((Integer) value);
}
}
});
return animator;
}
} else if (view == mTopBarView) {
int startTransY = (int) endValues.values.get(PROPNAME_TOP_BAR_TRANSITION_Y);
int endTransY = (int) startValues.values.get(PROPNAME_TOP_BAR_TRANSITION_Y);
if (startTransY != endTransY) {
ValueAnimator animator = ValueAnimator.ofInt(startTransY, endTransY);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Object value = animation.getAnimatedValue();
if (null != value) {
view.setTranslationY((Integer) value);
}
}
});
return animator;
}
}
return null;
}
}
最後把自定義的轉場動畫設置上即可:
getWindow().setEnterTransition(new CommentEnterTransition(this, mTitleBarTxt, mBottomSendBar));
再看看效果:
首先一點可以明確指示一個分享元素的轉場動畫,
一開始想用原始的 ChangeBounds 來簡單實現但是有一個問題就是從圓形變成矩形的這個過程太過生硬
後來沒辦法只能自己自動轉存動畫.
我們在來分析看看上面的那個轉場動畫
1. 揭露效果: 從第一個頁面的圓球開始, 圓球慢慢的方法, 直到整個動畫結束
2. 看似 View 是在隨著動畫的過程慢慢放大
3. 似乎還有曲線位移的動畫?
其實如果真的自己寫過轉場動畫的話, 第二個可以排除了, 分享元素的轉場動畫一開始 view 就已經變成第二個頁面中的View 了, 所以 View 沒有放大過程
ok 我們首先來解決第一個問題, 揭露動畫. 這個是 Android 5.0 以後原生提供的一個動畫使用起來特別簡單:
ViewAnimationUtils.createCircularReveal(view, centerX, centerY,startRadius, endRadius);
其中
centerX
, centerY
是揭露動畫圓心的位置;
startRadius
, endRadius
則是開始時圓球的半徑 和結束時圓球的半徑
那麼加來我們就需要 繼承 Transition
來實現揭露動畫了.
繼承 Transition
需要重寫以下 3個方法:
1. public void captureStartValues(TransitionValues transitionValues)
這裡能夠獲取到 上一個頁面的對應的 View 一些屬性值
2. public void captureEndValues(TransitionValues transitionValues)
這裡能夠獲取到 即將要打開的對應的頁面的對應 View 的一些屬性值
3. public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)
這裡創建對應的轉場動畫
ok, 那麼為了實現 上面的揭露動畫, 我們需要一開始獲取的值有:
1. 上一個頁面的圓形 View 的寬度, 作為揭露動畫圓形的最開始的半徑
2. 即將要打開的頁面的 對應 View 的對角線的長度, 作為揭露動畫圓形的最終半徑
3. 獲取揭露動畫的圓心位置, 這裡我們去 View 的中間位置
ok 那麼就可實現一個簡單的轉場揭露動畫了, 先看看代碼, 然後再看看效果
代碼:
public class ShareElemEnterRevealTransition extends Transition {
private static final String TAG = "ShareElemEnterRevealTransition";
private static final String PROPNAME_RADIUS = "custom_reveal:change_radius:radius";
private boolean hasAnim = false;
private View animView;
public ShareElemEnterRevealTransition(View animView) {
this.animView = animView;
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
transitionValues.values.put(PROPNAME_RADIUS, transitionValues.view.getWidth() / 2);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
View view = transitionValues.view;
float widthSquared = view.getWidth() * view.getWidth();
float heightSquared = view.getHeight() * view.getHeight();
int radius = (int) Math.sqrt(widthSquared + heightSquared) / 2;
transitionValues.values.put(PROPNAME_RADIUS, radius);
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
if (null == startValues || null == endValues) {
return null;
}
final View view = endValues.view;
int startRadius = (int) startValues.values.get(PROPNAME_RADIUS);
int endRadius = (int) endValues.values.get(PROPNAME_RADIUS);
if (view == animView) {
Animator reveal = createAnimator(view, startRadius, endRadius);
hasAnim = true;
return reveal;
}
return null;
}
private Animator createAnimator(View view, float startRadius, float endRadius) {
int centerX = view.getWidth() / 2;
int centerY = view.getHeight() / 2;
Animator reveal = ViewAnimationUtils.createCircularReveal(view, centerX, centerY,
startRadius, endRadius);
return new NoPauseAnimator(reveal);
}
}
效果:
貌似差距有點大,沒有顏色的漸變, 同時貌似接通動畫的圓形是在第二頁面 View 的圓心, 沒有移動的感覺
ok , 接下來需要處理的有:
1. 移動 View
2. 背景顏色漸變
通過上面的代碼示例, 做這兩件事情應該不復雜, 下面直接上代碼:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrjEseTR1cmrOjwvcD4NCjxwcmUgY2xhc3M9"brush:java;">
public class ChangeColor extends Transition {
private static final String TAG = "ChangeColor";
private static final String PROPNAME_BACKGROUND = "customtransition:change_color:backgroundcolor";
int mStartColor;
int mEndColor;
public ChangeColor(int startColor, int endColor) {
this.mStartColor = startColor;
this.mEndColor = endColor;
}
private void captureValues(TransitionValues values) {
values.values.put(PROPNAME_BACKGROUND, values.view.getBackground());
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
transitionValues.values.put(PROPNAME_BACKGROUND, mStartColor);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
transitionValues.values.put(PROPNAME_BACKGROUND, mEndColor);
}
@Override
public Animator createAnimator(ViewGroup sceneRoot,
TransitionValues startValues, TransitionValues endValues) {
if (null == startValues || null == endValues) {
return null;
}
final View view = endValues.view;
int startColor = (int) startValues.values.get(PROPNAME_BACKGROUND);
int endColor = (int) endValues.values.get(PROPNAME_BACKGROUND);
if (startColor != endColor) {
ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(), startColor, endColor);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Object value = animation.getAnimatedValue();
if (null != value) {
view.setBackgroundColor((Integer) value);
}
}
});
return animator;
}
return null;
}
}
改變位置(曲線運動):
public class ChangePosition extends Transition {
private static final String TAG = "ChangePosition";
private static final String PROPNAME_POSITION = "custom_position:change_position:position";
public ChangePosition() {
// 這裡通過曲線的方式 來改變位置
setPathMotion(new PathMotion() {
@Override
public Path getPath(float startX, float startY, float endX, float endY) {
Path path = new Path();
path.moveTo(startX, startY);
float controlPointX = (startX + endX) / 3;
float controlPointY = (startY + endY) / 2;
// 這裡是一條貝塞爾曲線的路基, (controlPointX, controlPointY) 表示控制點
path.quadTo(controlPointX, controlPointY, endX, endY);
return path;
}
});
}
private void captureValues(TransitionValues values) {
values.values.put(PROPNAME_POSITION, values.view.getBackground());
Rect rect = new Rect();
values.view.getGlobalVisibleRect(rect);
values.values.put(PROPNAME_POSITION, rect);
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
@Override
public Animator createAnimator(ViewGroup sceneRoot,
TransitionValues startValues, TransitionValues endValues) {
if (null == startValues || null == endValues) {
return null;
}
if (startValues.view.getId() > 0) {
Rect startRect = (Rect) startValues.values.get(PROPNAME_POSITION);
Rect endRect = (Rect) endValues.values.get(PROPNAME_POSITION);
final View view = endValues.view;
Path changePosPath = getPathMotion().getPath(startRect.centerX(), startRect.centerY(), endRect.centerX(), endRect.centerY());
int radius = startRect.centerY() - endRect.centerY();
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(view, new PropPosition(PointF.class, "position", new PointF(endRect.centerX(), endRect.centerY())), null, changePosPath);
objectAnimator.setInterpolator(new FastOutSlowInInterpolator());
return objectAnimator;
}
return null;
}
static class PropPosition extends Property {
public PropPosition(Class type, String name) {
super(type, name);
}
public PropPosition(Class type, String name, PointF startPos) {
super(type, name);
this.startPos = startPos;
}
PointF startPos;
@Override
public void set(View view, PointF topLeft) {
int x = Math.round(topLeft.x);
int y = Math.round(topLeft.y);
int startX = Math.round(startPos.x);
int startY = Math.round(startPos.y);
int transY = y - startY;
int transX = x - startX;
// 這裡控制 View 移動
view.setTranslationX(transX);
view.setTranslationY(transY);
}
@Override
public PointF get(View object) {
return null;
}
}
}
上面改變位置的 使用 Path 動畫, 使得 View 能夠以貝塞爾曲線的方式進行位移
ok 上面基本上就把 enter 的動畫處理完了, 但是返回還是有點問題.
看下圖:
返回的時候 View 大小已經變成了後面個頁面 View 的大小了, 然後由於大小的限制揭露動畫基本也看不出效果.
所以分享元素的返回動畫我們也要做一些細微的調整.
關於改變 View大小的這個問題, 我看了下 ChangeBounds 的源碼, 然後發現, 他們是通過調用 View一個隱藏方法:
/**
* Same as setFrame, but public and hidden. For use in {@link android.transition.ChangeBounds}.
* @hide
*/
public void setLeftTopRightBottom(int left, int top, int right, int bottom) {
setFrame(left, top, right, bottom);
}
後面發現有適配問題, 這個方法只在5.1或以上才有, 在5.0 上面沒有, 然後又看了下 5.0 的ChangeBounds 源碼, 發現在 5.0 上改變 View 大小是通過以下方式實現的:
view.setLeft(left);
view.setRight(right);
view.setTop(top);
view.setBottom(bottom);
額 其實 5.0 的這個 set 方法在 5.1或以上也是可以使用的.
所以改變 View 大小這個, 可以選擇 5.1 以上用反射 調用 setLeftTopRightBottom 方法, 也可以選擇 都直接使用set 方法
下面貼上返回動畫一些代碼:
改變位置:
public class ShareElemReturnChangePosition extends Transition {
private static final String TAG = "ShareElemReturnChangePosition";
private static final String PROPNAME_POSITION = "custom_position:change_position:position";
public ShareElemReturnChangePosition() {
setPathMotion(new PathMotion() {
@Override
public Path getPath(float startX, float startY, float endX, float endY) {
Path path = new Path();
path.moveTo(startX, startY);
float controlPointX = (startX + endX) / 3;
float controlPointY = (startY + endY) / 2;
path.quadTo(controlPointX, controlPointY, endX, endY);
return path;
}
});
}
private void captureValues(TransitionValues values) {
values.values.put(PROPNAME_POSITION, values.view.getBackground());
Rect rect = new Rect();
values.view.getGlobalVisibleRect(rect);
values.values.put(PROPNAME_POSITION, rect);
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
@Override
public Animator createAnimator(ViewGroup sceneRoot,
TransitionValues startValues, TransitionValues endValues) {
if (null == startValues || null == endValues) {
return null;
}
if (startValues.view.getId() > 0) {
Rect startRect = (Rect) startValues.values.get(PROPNAME_POSITION);
Rect endRect = (Rect) endValues.values.get(PROPNAME_POSITION);
final View view = endValues.view;
Rect rect = new Rect();
view.getGlobalVisibleRect(rect);
Path changePosPath = getPathMotion().getPath(startRect.centerX(), startRect.centerY(), endRect.centerX(), endRect.centerY() - endRect.height() / 2);
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(view, new PropPosition(PointF.class, "position", new PointF(startRect.centerX(), startRect.centerY())), null, changePosPath);
objectAnimator.setInterpolator(new FastOutSlowInInterpolator());
return objectAnimator;
}
return null;
}
static class PropPosition extends Property {
public PropPosition(Class type, String name) {
super(type, name);
}
public PropPosition(Class type, String name, PointF startPos) {
super(type, name);
this.startPos = startPos;
}
PointF startPos;
@Override
public void set(View view, PointF topLeft) {
int x = Math.round(topLeft.x);
int y = Math.round(topLeft.y);
int startX = Math.round(startPos.x);
int startY = Math.round(startPos.y);
int transY = y - startY;
int transX = x - startX;
Rect rect = new Rect();
view.getGlobalVisibleRect(rect);
view.setTranslationX(transX);
view.setTranslationY(transY);
}
@Override
public PointF get(View object) {
return null;
}
}
}
揭露動畫:
public class ShareElemReturnRevealTransition extends Transition {
private static final String TAG = "ShareElemReturnRevealTransition";
private static final String PROPNAME_BACKGROUND = "custom_reveal:change_radius:radius";
private boolean hasAnim = false;
private View animView;
private Rect startRect;
private Rect endRect;
public ShareElemReturnRevealTransition(View animView) {
this.animView = animView;
startRect = new Rect();
endRect = new Rect();
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
View view = transitionValues.view;
float widthSquared = view.getWidth() * view.getWidth();
float heightSquared = view.getHeight() * view.getHeight();
int radius = (int) Math.sqrt(widthSquared + heightSquared) / 2;
transitionValues.values.put(PROPNAME_BACKGROUND, radius);
transitionValues.view.getGlobalVisibleRect(startRect);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
transitionValues.view.getLocalVisibleRect(endRect);
transitionValues.values.put(PROPNAME_BACKGROUND, transitionValues.view.getWidth() / 2);
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {
if (null == startValues || null == endValues) {
return null;
}
final View view = endValues.view;
int startRadius = (int) startValues.values.get(PROPNAME_BACKGROUND);
int endRadius = (int) endValues.values.get(PROPNAME_BACKGROUND);
// 在執行返回動畫的時候, View 默認的被控制 為 前一個頁面的 ShareElem 的打消了
// 所以這裡 需要改變的 View 大小 才能 正常的使用揭露動畫
// 反射調用
relfectInvoke(view,
startRect.left,
startRect.top,
startRect.right,
startRect.bottom
);
Animator reveal = createAnimator(view, startRadius, endRadius);
// 在動畫的最後 被我們放大後的 View 會閃一些 這裡可以有防止那種情況發生
reveal.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
view.setClipBounds(new Rect(0, 0, 1, 1));
view.setVisibility(View.GONE);
}
});
return reveal;
}
private Animator createAnimator(View view, float startRadius, float endRadius) {
int centerX = view.getWidth() / 2;
int centerY = view.getHeight() / 2;
Animator reveal = ViewAnimationUtils.createCircularReveal(view, centerX, centerY,
startRadius, endRadius);
return new ShareElemEnterRevealTransition.NoPauseAnimator(reveal);
}
// setLeftTopRightBottom 需要反射執行, 該方法能夠控制 View 的大小以及位置 在 ChangeBounds 類中有調用
private void relfectInvoke(View view, int left, int top, int right, int bottom) {
Class clazz = view.getClass();
try {
Method m1 = clazz.getMethod("setLeftTopRightBottom", new Class[]{int.class, int.class, int.class, int.class});
m1.invoke(view, left, top, right, bottom);
} catch (Exception e) {
e.printStackTrace();
// 5.0版本 沒有 setLeftTopRightBottom 這個方法 使用一下方法 ,額 其實 5.0 以上也可以用這些方法?
view.setLeft(left);
view.setRight(right);
view.setTop(top);
view.setBottom(bottom);
}
}
}
ok 下面貼上 Activity 中如何使用這些動畫:
public class CommentActivity extends AppCompatActivity {
private static final String TAG = "CommentActivity";
View mBottomSendBar;
View mTitleBarTxt;
View mCommentBox;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_coment);
mCommentBox = findViewById(R.id.comment_box);
mTitleBarTxt = findViewById(R.id.txt_title_bar);
mBottomSendBar = findViewById(R.id.bottom_send_bar);
setTransition();
}
private void setTransition() {
// 頂部 title 和底部輸入框的進入動畫
getWindow().setEnterTransition(new CommentEnterTransition(this, mTitleBarTxt, mBottomSendBar));
getWindow().setSharedElementEnterTransition(buildShareElemEnterSet());
getWindow().setSharedElementReturnTransition(buildShareElemReturnSet());
}
/**
* 分享 元素 進入動畫
* @return
*/
private TransitionSet buildShareElemEnterSet() {
TransitionSet transitionSet = new TransitionSet();
Transition changePos = new ChangePosition();
changePos.setDuration(300);
changePos.addTarget(R.id.comment_box);
transitionSet.addTransition(changePos);
Transition revealTransition = new ShareElemEnterRevealTransition(mCommentBox);
transitionSet.addTransition(revealTransition);
revealTransition.addTarget(R.id.comment_box);
revealTransition.setInterpolator(new FastOutSlowInInterpolator());
revealTransition.setDuration(300);
ChangeColor changeColor = new ChangeColor(getResources().getColor(R.color.black_85_alpha), getResources().getColor(R.color.white));
changeColor.addTarget(R.id.comment_box);
changeColor.setDuration(350);
transitionSet.addTransition(changeColor);
transitionSet.setDuration(900);
return transitionSet;
}
/**
* 分享元素返回動畫
* @return
*/
private TransitionSet buildShareElemReturnSet() {
TransitionSet transitionSet = new TransitionSet();
Transition changePos = new ShareElemReturnChangePosition();
changePos.addTarget(R.id.comment_box);
transitionSet.addTransition(changePos);
ChangeColor changeColor = new ChangeColor(getResources().getColor(R.color.white), getResources().getColor(R.color.black_85_alpha));
changeColor.addTarget(R.id.comment_box);
transitionSet.addTransition(changeColor);
Transition revealTransition = new ShareElemReturnRevealTransition(mCommentBox);
revealTransition.addTarget(R.id.comment_box);
transitionSet.addTransition(revealTransition);
transitionSet.setDuration(900);
return transitionSet;
}
}
ok 關於自定義過場動畫基本就說完了。這裡沒有將具體如果使用過場動畫, 也沒有有說 EnterTransition 和 ReturnTransition 這些關系什麼的,還有如何最基本使用過場動畫什麼的, 這些Android 官網上都有中文文檔, 就不多提了
自微信出現以來取得了很好的成績,語音對講的實現更加方便了人與人之間的交流。今天來實踐一下微信的語音對講的錄音實現,這個也比較容易實現。在此,我將該按鈕封裝成為一個控件,並
Android RecyclerView 是Android5.0推出來的,導入support-v7包即可使用。個人體驗來說,RecyclerView絕對是一款功能強大的控
介紹在小米的開機動畫和一些歡迎界面中, 我們經常看到這種閃閃發光的流光字體。看起來很炫酷,其實實現原理相當簡單,我們只需要寫自定義控件繼承TextView,然後使用渲染器
一、場景描述:近期開發中遇到個問題,就是我們在做橫豎屏切換的功能時,橫豎屏布局是操作系統去感知的,作為開發員沒法確定Activity在什麼時候加載橫屏布局,在什麼時候加載