最近在看Cyril Mottier(公认的安卓开发大牛)大神的博客的时候看到了一篇文章,是介绍他们公司研发的CapitaineTrain(欧洲一款很受欢迎的铁路购票客户端)项目中实现的一个比较酷的效果。先上两张效果图
效果是不是很酷!从GooglePlay下载了CapitaineTrain安装后看了下操作,整个软件非常简洁,操作性特别强,人性化,动画流畅,比我国某铁的购票客户端强的太多了。是不是迫不及待的想实现这个效果?那就跟着我一起来做~
这个是原博客地址:http://cyrilmottier.com/2014/05/20/custom-animations-with-fragments/
起初只是想简单的实现下动画切换的效果,但是使用过Cyril大神开发的CapitaineTrain后感觉界面太清爽了,感叹安卓也能做出如此流畅的动画(这里肯定会有很多人说我太菜了~~~)!我这个人有个缺点,就是每当看到一个软件某个效果很好时就迫不及待的想去实现,另外说下我这个人有强迫症,如果是模仿的话也要尽量跟原客户端保持一致的效果,这款软件也不例外~花了一个多星期终于实现了自己想要的效果。由于我机器的安卓模拟器太卡了也就没有录制gif,跟上面的效果差不多。下面是我做的一个效果,只是静态图
我的实现步骤是这样的:首先是软件顶部导航的实现,然后是导航栏下面的布局实现,最后是动画的实现。
1、导航栏的实现
导航栏采用的是ActionBar来实现的,之前一直都用的自定义LinearLayout或者RelativeLayout来实现的,试着来实现一把。
其实5.0版本之后推出了ToolBar这个控件,跟ActionBar差不多,这里就不作介绍了。
导航栏分为这几个部分:导航栏和状态栏的颜色、导航栏左边的三个图标、图标下方的指示条、右边的Menu按钮。
(1)导航栏和状态栏的颜色
下面是我反编译CapitaineTrain后拿出来的资源文件(提起反编译感觉有点不太厚道):
<style name="Base.Theme.CapitaineTrain" parent="@style/Theme.AppCompat.Light.DarkActionBar">
......
<item name="colorPrimary">@color/ct_green</item>
<item name="colorPrimaryDark">@color/ct_dark_green</item>
......
</style>
colorPrimary属性定义了导航栏的颜色,colorPrimaryDark是5.0以后Material Design才有的属性,请看下面一张图
写好style之后别忘了在Manifest中应用该样式
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Base.Theme.CapitaineTrain" >
<activity
android:name="com.ldw.capitainetrain.ui.MainActivity"
android:label="@string/app_name"
android:theme="@style/Theme.CapitaineTrain.EmptyActionBar.NoInset" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
至于关于MaterialDesign的一些设计大家可以参考http://www.google.com/design/spec/style/color.html#
(2)导航栏左边三个icon和底部指示条的实现
本来是想自己修改下PagerSlidingTabStrip这个开源控件,但在github上无意中看到了https://github.com/Mirkoddd/TabBarView这个开源控件就直接拿过来用了,但是我也做了些许修改,包括指示条的高度,去掉icon中间的分割线,icon选中时的状态标识,icon长按时的Toast提示。下面上代码:
TabBarView类
package com.ldw.capitainetrain.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.LinearLayout;
import com.ldw.capitainetrain.R;
public class TabBarView extends LinearLayout {
public interface IconTabProvider {
public int getPageIconResId(int position);
}
private static final int STRIP_HEIGHT = 2;
public final Paint mPaint;
private int mStripHeight;
private float mOffset = 0f;
public static int mSelectedTab = 0;
public ViewPager pager;
public static int tabCount;
private final PageListener pageListener = new PageListener();
public OnPageChangeListener delegatePageListener;
private TabView child;
private View nextChild;
public static int a;
private Context mContext;
private int mToolBarHeight;
public TabBarView(Context context) {
this(context, null);
}
public TabBarView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.actionBarTabBarStyle);
}
public TabBarView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
setWillNotDraw(false);
mPaint = new Paint();
mPaint.setColor(Color.WHITE);
mPaint.setAntiAlias(true);
mStripHeight = (int) (STRIP_HEIGHT
* getResources().getDisplayMetrics().density + .5f);
mToolBarHeight = context.getResources().getDimensionPixelSize(
R.dimen.toolbar_height);
}
public void setStripColor(int color) {
if (mPaint.getColor() != color) {
mPaint.setColor(color);
invalidate();
}
}
public void setStripHeight(int height) {
if (mStripHeight != height) {
mStripHeight = height;
invalidate();
}
}
public void setSelectedTab(int tabIndex) {
if (tabIndex < 0) {
tabIndex = 0;
}
final int childCount = getChildCount();
if (tabIndex >= childCount) {
tabIndex = childCount - 1;
}
if (mSelectedTab != tabIndex) {
mSelectedTab = tabIndex;
invalidate();
}
}
public void setOffset(int position, float offset) {
if (mOffset != offset) {
mOffset = offset;
invalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw the strip manually
child = (TabView) getChildAt(mSelectedTab);
int height = getHeight();
if (child != null) {
float left = child.getLeft();
float right = child.getRight();
if (mOffset > 0f && mSelectedTab < tabCount - 1) {
nextChild = getChildAt(mSelectedTab + 1);
if (nextChild != null) {
final float nextTabLeft = nextChild.getLeft();
final float nextTabRight = nextChild.getRight();
left = (mOffset * nextTabLeft + (1f - mOffset) * left);
right = (mOffset * nextTabRight + (1f - mOffset) * right);
}
}
canvas.drawRect(left, height - mStripHeight, right, height, mPaint);
}
}
public void setViewPager(ViewPager pager) {
this.pager = pager;
if (pager.getAdapter() == null) {
throw new IllegalStateException(
"ViewPager does not have adapter instance.");
}
pager.setOnPageChangeListener(pageListener);
notifyDataSetChanged();
}
private class PageListener implements OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
mSelectedTab = position;
mOffset = positionOffset;
invalidate();
if (delegatePageListener != null) {
delegatePageListener.onPageScrolled(position, positionOffset,
positionOffsetPixels);
}
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
}
if (delegatePageListener != null) {
delegatePageListener.onPageScrollStateChanged(state);
}
}
@Override
public void onPageSelected(int position) {
if (delegatePageListener != null) {
delegatePageListener.onPageSelected(position);
}
changeIconAlpha(position);
}
}
public void notifyDataSetChanged() {
this.removeAllViews();
tabCount = pager.getAdapter().getCount();
for (int i = 0; i < tabCount; i++) {
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
addTabViewP(i, pager.getAdapter().getPageTitle(i).toString(),
((IconTabProvider) pager.getAdapter())
.getPageIconResId(i));
} else {
addTabViewL(i, pager.getAdapter().getPageTitle(i).toString(),
((IconTabProvider) pager.getAdapter())
.getPageIconResId(i));
}
}
getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
@SuppressLint("NewApi")
@Override
public void onGlobalLayout() {
getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
mSelectedTab = pager.getCurrentItem();
//更改icon的alpha值
changeIconAlpha(mSelectedTab);
}
});
}
private void changeIconAlpha(int position) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TabView tabView = (TabView) getChildAt(i);
float alpha = 0;
if (position == i) {
alpha = 1.0f;
} else {
alpha = 0.75f;
}
tabView.setIconAlpha(alpha);
}
}
private void addTabViewL(final int i, String string, int pageIconResId) {
// TODO Auto-generated method stub
TabView tab = new TabView(getContext());
// tab.setIcon(pageIconResId);
tab.setText(string, pageIconResId);
tab.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
pager.setCurrentItem(i);
}
});
this.addView(tab);
}
@SuppressLint("NewApi")
private void addTabViewP(final int i, final String string, int pageIconResId) {
// TODO Auto-generated method stub
final TabView tab = new TabView(getContext());
tab.setContentDescription(string);
tab.setIcon(pageIconResId);
if (android.os.Build.VERSION.SDK_INT >= 5) {
// int[] attrs = new int[] { android.R.attr.selectableItemBackground };
// TypedArray ta = mContext.obtainStyledAttributes(attrs);
// Drawable drawableFromTheme = ta.getDrawable(0);
// ta.recycle();
// tab.setBackground(drawableFromTheme);
tab.setBackgroundResource(R.drawable._selector_dark_borderless);
}
tab.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
pager.setCurrentItem(i);
}
});
// CheatSheet.setup(tab, string);
this.addView(tab, new LayoutParams(LayoutParams.WRAP_CONTENT,
mToolBarHeight));
}
public void setOnPageChangeListener(OnPageChangeListener listener) {
this.delegatePageListener = listener;
}
}
tab.setBackgroundResource(R.drawable._selector_dark_borderless);这句话是我加上的5.0上面的ripple效果。
TabView类:
package com.ldw.capitainetrain.view;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.TextView;
public class TabView extends LinearLayout {
private ImageView mImageView;
private TextView mTextView;
private OnLongClickListener mLongClickListener;
public TabView(Context context) {
this(context, null);
}
public TabView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.actionBarTabStyle);
}
public TabView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.actionBarTabTextStyle, outValue, true);
int txtstyle = outValue.data;
int pad = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources()
.getDisplayMetrics());
mImageView = new ImageView(context);
mImageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
mImageView.setScaleType(ScaleType.CENTER_INSIDE);
mTextView = new TextView(context);
mTextView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mTextView.setGravity(Gravity.CENTER);
mTextView.setCompoundDrawablePadding(pad);
mTextView.setTextAppearance(context, txtstyle);;
this.addView(mImageView);
this.addView(mTextView);
this.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mLongClickListener = new OnLongClickToastListener(this);
}
public void setIcon(int resId) {
setIcon(getContext().getResources().getDrawable(resId));
setOnLongClickListener(mLongClickListener);
}
public void setIcon(Drawable icon) {
if (icon != null) {
mImageView.setVisibility(View.VISIBLE);
mImageView.setImageDrawable(icon);
} else {
mImageView.setImageResource(View.GONE);
}
}
public void setText(int resId, int ico) {
setText(getContext().getString(resId), ico);
}
public void setText(CharSequence text, int ico) {
mTextView.setText(text);
mTextView.setCompoundDrawablesWithIntrinsicBounds(ico, 0, 0, 0);;
}
public void setIconAlpha(float alpha) {
mImageView.setAlpha(alpha);
}
}
OnLongClickToastListener类:
/**
* Copyright (c) www.longdw.com
*/
package com.ldw.capitainetrain.view;
import android.content.Context;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.widget.Toast;
public class OnLongClickToastListener implements OnLongClickListener {
private TabView mTabView;
public OnLongClickToastListener(TabView tabView) {
mTabView = tabView;
}
@Override
public boolean onLongClick(View v) {
int[] location = new int[2];
mTabView.getLocationOnScreen(location);
Context context = mTabView.getContext();
int width = mTabView.getWidth();
int height = mTabView.getHeight();
int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
CharSequence cs = mTabView.getContentDescription();
Toast toast = Toast.makeText(context, cs, Toast.LENGTH_SHORT);
int x = location[0];
//因为默认的Toast是出于屏幕中间位置
int xOffset = x + width / 2 - screenWidth / 2;
toast.setGravity(49, xOffset, height);
toast.show();
return true;
}
}
下面是程序的入口类MainActivity类:
package com.ldw.capitainetrain.ui;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import com.ldw.capitainetrain.R;
import com.ldw.capitainetrain.view.TabBarView;
import com.ldw.capitainetrain.view.TabBarView.IconTabProvider;
public class MainActivity extends ActionBarActivity {
private ViewPager mViewPager;
private SectionsPagerAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
// setSupportActionBar(toolbar);
getSupportActionBar().setDisplayShowTitleEnabled(false);
getSupportActionBar().setDisplayShowHomeEnabled(false);
getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
// int toolBarHeight = getResources().getDimensionPixelSize(R.dimen.toolbar_height);
TabBarView tbv = (TabBarView) getLayoutInflater().inflate(R.layout.toolbar_layout, null);
// toolbar.addView(tbv, new LayoutParams(LayoutParams.WRAP_CONTENT, toolBarHeight));
getSupportActionBar().setCustomView(tbv);
mViewPager = (ViewPager) findViewById(R.id.viewpager);
mAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mAdapter);
tbv.setViewPager(mViewPager);
}
public class SectionsPagerAdapter extends FragmentPagerAdapter implements IconTabProvider{
private int[] mDrawable = { R.drawable.ic_tab_search, R.drawable.ic_tab_cart, R.drawable.ic_tab_tickets };
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
return new SearchFragment();
}
@Override
public int getCount() {
// Show 3 total pages.
return mDrawable.length;
}
@Override
public int getPageIconResId(int position) {
return mDrawable[position];
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "Search";
case 1:
return "Cart";
case 2:
return "Tickets";
}
return null;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
对了,在开发的时候遇到个问题,就是icon图标总是左边总是有距离
为了去掉这个间距,我们可以在style中添加如下代码:
<style name="Widget.ToolBar" parent="@style/Widget.AppCompat.Toolbar">
<item name="maxButtonHeight">?actionBarSize</item>
</style>
<style name="Widget.ToolBar.NoInset" parent="@style/Widget.ToolBar">
<item name="contentInsetStart">0.0dip</item>
</style>
<style name="Theme.CapitaineTrain.EmptyActionBar.NoInset" parent="@style/Theme.CapitaineTrain.EmptyActionBar">
<item name="toolbarStyle">@style/Widget.ToolBar.NoInset</item>
</style>
主要是contentInsetStart属性。通过以上代码我们可以实现导航栏切换的效果。接下来我们来实现点击输入框时候的动画效果。
2、动画的实现
动画实现主要涉及到以下几个核心的知识点。
(1)通过ViewPropertyAnimator来实现动画;
(2)动画需要手动计算开始和结束的位置,先通过View#getDrawingRect(Rect)计算view在父坐标系中的位置,然后通过ViewGroup#offsetDescendantRectToMyCoords(View, Rect)来计算在根视图(按作者的说法是ancestor coordinate system)中的位置;
(3)通过AnimatorListenerAdapter来优化动画。
上代码,SearchFragment类
/**
* Copyright (c) www.longdw.com
*/
package com.ldw.capitainetrain.ui;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.ldw.capitainetrain.R;
import com.ldw.capitainetrain.anim.CustomAnimator;
import com.ldw.capitainetrain.anim.LayerEnablingAnimatorListener;
public class SearchFragment extends Fragment implements OnClickListener {
private TimeInterpolator ANIMATION_INTERPOLATOR = new DecelerateInterpolator();
private int ANIMATION_DURATION = 500;
private TextView mOutwardTv;
private TextView mInwardTv;
private FrameLayout mMainContainer;
private LinearLayout mDateTimeContainer;
private LinearLayout mStationsContainer;
private FrameLayout mFirstSpacer;
private FrameLayout mSencondSpacer;
private LinearLayout mPassengersContainer;
private FrameLayout mThirdSpacer;
private Button mSearchBtn;
private int mHalfHeight;
private FrameLayout mEditorLayout;
private CustomAnimator mAnimator;
private Button mArrivalBtn;
private Button mDepatureBtn;
private EditText mDepatureEt;
private EditText mArrivalEt;
private int mCurrentSelectedViewId;
private InputMethodManager mInputManager;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater
.inflate(R.layout.fragment_search, container, false);
mAnimator = new CustomAnimator(getActivity());
mInputManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight,
int oldBottom) {
v.removeOnLayoutChangeListener(this);
mHalfHeight = view.getHeight() / 2;
mEditorLayout.setTranslationY(mHalfHeight);
mEditorLayout.setAlpha(0f);
mAnimator.setEditModeHalfHeight(mHalfHeight);
}
});
initView(view);
getFragmentManager().beginTransaction().add(R.id.edit_mode_fragment_container, new OtherFragment()).commit();
return view;
}
private void initView(View view) {
mOutwardTv = (TextView) view.findViewById(R.id.outward);
mOutwardTv.setOnClickListener(this);
mInwardTv = (TextView) view.findViewById(R.id.inward);
mInwardTv.setOnClickListener(this);
mMainContainer = (FrameLayout) view.findViewById(R.id.main_container);
mStationsContainer = (LinearLayout) view
.findViewById(R.id.stations_container);
mDateTimeContainer = (LinearLayout) view
.findViewById(R.id.date_time_container);
mFirstSpacer = (FrameLayout) view.findViewById(R.id.first_spacer);
mSencondSpacer = (FrameLayout) view.findViewById(R.id.second_spacer);
mPassengersContainer = (LinearLayout) view
.findViewById(R.id.passengers_container);
mThirdSpacer = (FrameLayout) view.findViewById(R.id.third_spacer);
mSearchBtn = (Button) view.findViewById(R.id.btn_search);
mEditorLayout = (FrameLayout) view.findViewById(R.id.edit_mode_container);
//From
mDepatureBtn = (Button) view.findViewById(R.id.btn_departure);
mDepatureBtn.setOnClickListener(this);
mDepatureEt = (EditText) view.findViewById(R.id.depature_et);
//To
mArrivalBtn = (Button) view.findViewById(R.id.btn_arrival);
mArrivalBtn.setOnClickListener(this);
mArrivalEt = (EditText) view.findViewById(R.id.arrival_et);
}
@Override
public void onClick(View v) {
if (v == mDepatureBtn) {
focusOn(mDepatureBtn, mStationsContainer, true);
focusOn(mDepatureBtn, mDateTimeContainer, true);
focusOn(mDepatureBtn, mSencondSpacer, true);
stickTo(mFirstSpacer, mDepatureBtn, true);
fadeOutToBottom(mPassengersContainer, true);
fadeOutToBottom(mSearchBtn, true);
fadeOutToBottom(mThirdSpacer, true);
slideInToTop(mEditorLayout, true);
mEditorLayout.setVisibility(View.VISIBLE);
mDepatureBtn.setVisibility(View.GONE);
mDepatureEt.setClickable(true);
mDepatureEt.setFocusable(true);
mDepatureEt.setFocusableInTouchMode(true);
mDepatureEt.requestFocus();
mInputManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
} else if (v == mArrivalBtn) {
focusOn(mArrivalBtn, mStationsContainer, true);
focusOn(mArrivalBtn, mDateTimeContainer, true);
focusOn(mDepatureBtn, mSencondSpacer, true);
stickTo(mFirstSpacer, mArrivalBtn, true);
fadeOutToBottom(mPassengersContainer, true);
fadeOutToBottom(mSearchBtn, true);
fadeOutToBottom(mThirdSpacer, true);
slideInToTop(mEditorLayout, true);
mEditorLayout.setVisibility(View.VISIBLE);
mArrivalBtn.setVisibility(View.GONE);
mArrivalEt.setClickable(true);
mArrivalEt.setFocusable(true);
mArrivalEt.setFocusableInTouchMode(true);
mArrivalEt.requestFocus();
mInputManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
} else if (v == mOutwardTv) {
focusOn(mOutwardTv, mStationsContainer, true);
focusOn(mOutwardTv, mDateTimeContainer, true);
focusOn(mOutwardTv, mFirstSpacer, true);
stickTo(mSencondSpacer, mOutwardTv, true);
fadeOutToBottom(mPassengersContainer, true);
fadeOutToBottom(mSearchBtn, true);
fadeOutToBottom(mThirdSpacer, true);
slideInToTop(mEditorLayout, true);
mEditorLayout.setVisibility(View.VISIBLE);
} else if(v == mInwardTv) {
focusOn(mInwardTv, mStationsContainer, true);
focusOn(mInwardTv, mDateTimeContainer, true);
focusOn(mInwardTv, mFirstSpacer, true);
stickTo(mSencondSpacer, mInwardTv, true);
fadeOutToBottom(mPassengersContainer, true);
fadeOutToBottom(mSearchBtn, true);
fadeOutToBottom(mThirdSpacer, true);
slideInToTop(mEditorLayout, true);
mEditorLayout.setVisibility(View.VISIBLE);
}
mCurrentSelectedViewId = v.getId();
((ActionBarActivity)getActivity()).startSupportActionMode(mCallback);
}
private final Rect mTmpRect = new Rect();
private void focusOn(View v, View movableView, boolean animated) {
v.getDrawingRect(mTmpRect);
mMainContainer.offsetDescendantRectToMyCoords(v, mTmpRect);
movableView.animate().
translationY(-mTmpRect.top).
setDuration(animated ? ANIMATION_DURATION : 0).
setInterpolator(ANIMATION_INTERPOLATOR).
setListener(new LayerEnablingAnimatorListener(movableView)).
start();
}
private void unfocus(View v, View movableView, boolean animated) {
movableView.animate().
translationY(0).
setDuration(animated ? ANIMATION_DURATION : 0).
setInterpolator(ANIMATION_INTERPOLATOR).
setListener(new LayerEnablingAnimatorListener(movableView)).
start();
}
private void fadeOutToBottom(View v, boolean animated) {
v.animate().
translationYBy(mHalfHeight).
alpha(0).
setDuration(animated ? ANIMATION_DURATION : 0).
setInterpolator(ANIMATION_INTERPOLATOR).
setListener(new LayerEnablingAnimatorListener(v)).
start();
}
private void fadeInToTop(View v, boolean animated) {
v.animate().
translationYBy(-mHalfHeight).
alpha(1).
setDuration(animated ? ANIMATION_DURATION : 0).
setInterpolator(ANIMATION_INTERPOLATOR).
setListener(new LayerEnablingAnimatorListener(v)).
start();
}
private void slideInToTop(View v, boolean animated) {
v.animate().
translationY(0).
alpha(1).
setDuration(animated ? ANIMATION_DURATION : 0).
setListener(new LayerEnablingAnimatorListener(v)).
setInterpolator(ANIMATION_INTERPOLATOR);
}
private void slideOutToBottom(View v, boolean animated) {
v.animate().
translationY(mHalfHeight * 2).
alpha(0).
setDuration(animated ? ANIMATION_DURATION : 0).
setListener(new LayerEnablingAnimatorListener(v)).
setInterpolator(ANIMATION_INTERPOLATOR);
}
private void stickTo(View v, View viewToStickTo, boolean animated) {
v.getDrawingRect(mTmpRect);
mMainContainer.offsetDescendantRectToMyCoords(v, mTmpRect);
v.animate().
translationY(viewToStickTo.getHeight() - mTmpRect.top).
setDuration(animated ? ANIMATION_DURATION : 0).
setInterpolator(ANIMATION_INTERPOLATOR).
start();
}
private void unstickFrom(View v, View viewToStickTo, boolean animated) {
v.animate().
translationY(0).
setDuration(animated ? ANIMATION_DURATION : 0).
setInterpolator(ANIMATION_INTERPOLATOR).
setListener(new LayerEnablingAnimatorListener(viewToStickTo)).
start();
}
private ActionMode.Callback mCallback = new ActionMode.Callback() {
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
mode.setTitle("From");
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
slideOutToBottom(mEditorLayout, true);
switch (mCurrentSelectedViewId) {
case R.id.btn_departure:
unstickFrom(mFirstSpacer, mDepatureBtn, true);
fadeInToTop(mPassengersContainer, true);
fadeInToTop(mSearchBtn, true);
fadeInToTop(mThirdSpacer, true);
unfocus(mDepatureBtn, mStationsContainer, true);
unfocus(mDepatureBtn, mDateTimeContainer, true);
unfocus(mDepatureBtn, mSencondSpacer, true);
mDepatureEt.setClickable(false);
mDepatureEt.setFocusable(false);
mDepatureBtn.setVisibility(View.VISIBLE);
mInputManager.hideSoftInputFromWindow(mDepatureEt.getWindowToken(), 0);
break;
case R.id.btn_arrival:
unstickFrom(mFirstSpacer, mArrivalBtn, true);
fadeInToTop(mPassengersContainer, true);
fadeInToTop(mSearchBtn, true);
fadeInToTop(mThirdSpacer, true);
unfocus(mArrivalBtn, mStationsContainer, true);
unfocus(mArrivalBtn, mDateTimeContainer, true);
unfocus(mArrivalBtn, mSencondSpacer, true);
mArrivalEt.setClickable(false);
mArrivalEt.setFocusable(false);
mArrivalBtn.setVisibility(View.VISIBLE);
mInputManager.hideSoftInputFromWindow(mArrivalEt.getWindowToken(), 0);
break;
case R.id.outward:
unstickFrom(mSencondSpacer, mOutwardTv, true);
fadeInToTop(mPassengersContainer, true);
fadeInToTop(mSearchBtn, true);
fadeInToTop(mThirdSpacer, true);
unfocus(mOutwardTv, mStationsContainer, true);
unfocus(mOutwardTv, mDateTimeContainer, true);
unfocus(mOutwardTv, mFirstSpacer, true);
break;
case R.id.inward:
unstickFrom(mSencondSpacer, mInwardTv, true);
fadeInToTop(mPassengersContainer, true);
fadeInToTop(mSearchBtn, true);
fadeInToTop(mThirdSpacer, true);
unfocus(mInwardTv, mStationsContainer, true);
unfocus(mInwardTv, mDateTimeContainer, true);
unfocus(mInwardTv, mFirstSpacer, true);
break;
}
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// mode.getMenuInflater().inflate(R.menu.main, menu);
//此处要返回true
return true;
// return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
};
}
fragment_search.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ScrollView
android:id="@+id/normal_mode_container"
style="@style/Form"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:paddingBottom="0.0dip"
android:paddingTop="0.0dip" >
<RelativeLayout
android:id="@+id/form_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingBottom="@dimen/spacing_large" >
<LinearLayout
android:id="@+id/stations_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_large"
android:background="@color/ct_white"
android:orientation="vertical" >
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_section_top"
android:layout_gravity="top"
android:background="@drawable/divider_section_top" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/departure_icon"
android:layout_width="wrap_content"
android:layout_height="@dimen/form_field_height"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:paddingLeft="@dimen/spacing_large"
android:scaleType="center"
android:src="@drawable/ic_search_from" />
<ImageView
android:id="@+id/arrival_icon"
android:layout_width="wrap_content"
android:layout_height="@dimen/form_field_height"
android:layout_alignParentLeft="true"
android:layout_below="@id/departure_icon"
android:layout_marginTop="@dimen/divider_medium"
android:paddingLeft="@dimen/spacing_large"
android:scaleType="center"
android:src="@drawable/ic_search_to" />
<FrameLayout
android:id="@+id/departure"
android:layout_width="match_parent"
android:layout_height="@dimen/form_field_height" >
<LinearLayout
android:id="@+id/departure_search_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<EditText
android:id="@+id/depature_et"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null"
android:clickable="false"
android:focusable="false"
android:hint="@string/ui_android_search_departure"
android:layout_marginLeft="50dip" />
</LinearLayout>
<Button
android:id="@+id/btn_departure"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null" />
</FrameLayout>
<View
android:id="@+id/stations_container_divider"
android:layout_width="match_parent"
android:layout_height="@dimen/divider_medium"
android:layout_below="@id/departure"
android:layout_marginLeft="@dimen/spacing_large"
android:layout_marginRight="@dimen/spacing_large"
android:background="@drawable/divider_medium" />
<FrameLayout
android:id="@+id/arrival"
android:layout_width="match_parent"
android:layout_height="@dimen/form_field_height"
android:layout_below="@id/stations_container_divider" >
<LinearLayout
android:id="@+id/arrival_search_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<EditText
android:id="@+id/arrival_et"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="50dip"
android:background="@null"
android:clickable="false"
android:focusable="false"
android:hint="@string/ui_android_search_arrival" />
</LinearLayout>
<Button
android:id="@+id/btn_arrival"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null" />
</FrameLayout>
<ImageButton
android:id="@+id/btn_swap_stations"
android:layout_width="@dimen/grid_size_medium"
android:layout_height="@dimen/grid_size_medium"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@null"
android:scaleType="center"
android:src="@drawable/ic_search_swap_stations"
android:visibility="gone" />
</RelativeLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/first_spacer"
android:layout_width="match_parent"
android:layout_height="@dimen/form_field_height"
android:layout_below="@id/stations_container"
android:background="@color/app_background" >
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_section_bottom"
android:layout_gravity="top"
android:background="@drawable/divider_section_bottom" />
</FrameLayout>
<LinearLayout
android:id="@+id/date_time_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/stations_container"
android:layout_marginTop="@dimen/spacing_large"
android:background="@color/ct_white"
android:orientation="vertical" >
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_section_top"
android:layout_gravity="top"
android:background="@drawable/divider_section_top" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@drawable/divider_medium"
android:dividerPadding="@dimen/spacing_large"
android:orientation="vertical"
android:showDividers="middle" >
<TextView
android:id="@+id/outward"
android:layout_width="match_parent"
android:layout_height="@dimen/form_field_height"
android:drawableLeft="@drawable/ic_search_outward"
android:drawablePadding="@dimen/spacing_medium"
android:ellipsize="end"
android:gravity="center_vertical"
android:hint="@string/ui_android_search_departureDate"
android:paddingLeft="@dimen/spacing_large"
android:paddingRight="@dimen/spacing_large"
android:singleLine="true"
android:textSize="@dimen/font_size_medium" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/form_field_height"
android:orientation="horizontal" >
<TextView
android:id="@+id/inward"
android:layout_width="0.0dip"
android:layout_height="match_parent"
android:layout_weight="1.0"
android:drawableLeft="@drawable/ic_search_inward"
android:drawablePadding="@dimen/spacing_medium"
android:ellipsize="end"
android:gravity="center_vertical"
android:hint="@string/ui_android_search_oneWay"
android:paddingLeft="@dimen/spacing_large"
android:paddingRight="@dimen/spacing_large"
android:singleLine="true"
android:textSize="@dimen/font_size_medium" />
<ImageButton
android:id="@+id/inward_clear_button"
android:layout_width="48.0dip"
android:layout_height="match_parent"
android:background="@drawable/_selector_light"
android:src="@drawable/ic_search_view_clear"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/second_spacer"
android:layout_width="match_parent"
android:layout_height="@dimen/form_field_height"
android:layout_below="@id/date_time_container"
android:background="@color/app_background" >
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_section_bottom"
android:layout_gravity="top"
android:background="@drawable/divider_section_bottom" />
</FrameLayout>
<LinearLayout
android:id="@+id/passengers_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/date_time_container"
android:layout_marginTop="@dimen/spacing_large"
android:background="@color/ct_white"
android:orientation="vertical" >
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_section_top"
android:layout_gravity="top"
android:background="@drawable/divider_section_top" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/form_field_height"
android:orientation="horizontal" >
<TextView
android:id="@+id/passengers"
android:layout_width="0.0dip"
android:layout_height="match_parent"
android:layout_weight="1.0"
android:drawableLeft="@drawable/ic_search_passengers"
android:drawablePadding="@dimen/spacing_medium"
android:ellipsize="end"
android:gravity="center_vertical"
android:hint="@string/ui_android_search_noPassengersSelected"
android:paddingLeft="@dimen/spacing_large"
android:paddingRight="@dimen/spacing_small"
android:singleLine="true"
android:textSize="@dimen/font_size_medium" />
<ImageButton
android:id="@+id/passengers_clear_button"
android:layout_width="48.0dip"
android:layout_height="match_parent"
android:background="@drawable/_selector_light"
android:src="@drawable/ic_search_view_clear"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/third_spacer"
android:layout_width="match_parent"
android:layout_height="@dimen/form_field_height"
android:layout_below="@id/passengers_container"
android:background="@color/app_background" >
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_section_bottom"
android:layout_gravity="top"
android:background="@drawable/divider_section_bottom" />
</FrameLayout>
<Button
android:id="@+id/btn_search"
style="@style/Button.Action.Green"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/passengers_container"
android:layout_marginLeft="@dimen/inset_horizontal_action_button"
android:layout_marginRight="@dimen/inset_horizontal_action_button"
android:layout_marginTop="@dimen/spacing_large"
android:background="@drawable/btn_green"
android:text="@string/ui_android_search_searchAction" />
</RelativeLayout>
</ScrollView>
<FrameLayout
android:id="@+id/edit_mode_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/form_field_height"
android:visibility="invisible" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:orientation="vertical"
android:paddingTop="@dimen/spacing_large" >
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_section_top"
android:layout_gravity="top"
android:layout_marginLeft="@dimen/inset_horizontal_content"
android:layout_marginRight="@dimen/inset_horizontal_content"
android:background="@drawable/divider_section_top" />
<FrameLayout
android:id="@+id/edit_mode_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</FrameLayout>
</FrameLayout>
以上就是我实现的过程。最后以Cyrial的话做个总结:
With the introduction of the new property-based animation framework and Fragments in Android 3.0, the framework provides developers with all the necessary tools to create wonderful and meaningful UIs while still keeping a maintainable and modularized code. Animating Fragments is generally a single ViewPropertyHolder API call away and may drastically improve the way users understand your application. Designing an application is not only about creating a nice static design. It is also about moving graphical elements in a way it is meaningful to users. Transitions both give life to an application and enrich user experience.





大牛你好~~~
有款应用 叫做Morning Routine ,大部分都是5.0以后Material Design 的效果,可以看下 ,非常好
感谢指导,马上下个瞧瞧
感谢指导,我马上下载个看看
怎么没有源码下载?
网站不错!