android 仿 ios 搜索界面跳转效果
最新写项目的时候,看到搜索界面的跳转基本都是点击搜索然后跳转到下个页面,android 微信上则是 类似toolbar的效果,而ios 上则是一个搜索框上移然后显示新界面的一个效果。仔细研究了下发现和android 的 共享元素的过渡实现 的效果很像,所以在此模仿下。但是 共享元素的过渡实现 是5.0以后才有的,兼容5.0一下需要自定义动画效果,查了些资料发现也是可以实现的。下面是效果图:
1.实现思路:
实现的思路也比较简单,大概的步骤如下:
1.确定第一个界面的共享元素,将其信息传递个第二个界面
2.第二个界面接收信息,开始的时候将界面设置为透明,并只显示共享元素。
3.将第二个界面的共享元素进行动画处理。
2.获取共享元素位置信息:
在第一个界面的xml 里面,搜索框直接用一个自定义的imageView 代替
2.1 自定义imageView
import android.content.Context;import android.util.AttributeSet;import android.widget.ImageView;/** * 自定义image,用于在4.x上实现仿5.0上分享元素的动画 * Created by lh on 2016/11/4. */public class CustomImage extends ImageView { private int mResId; public CustomImage(Context context) { this(context, null, 0); } public CustomImage(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomImage(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); if (attrs != null) { String namespace = "http://schemas.android.com/apk/res/android"; String attribute = "src"; mResId = attrs.getAttributeResourceValue(namespace, attribute, 0); } } public int getImageId() { return mResId; } @Override public void setImageResource(int resId) { super.setImageResource(resId); mResId = resId; }}
2.2 点击事件的处理
在第一个界面中,我们需要获取到共享元素的位置信息,并将其传递给下一个界面。
private void showShareAnimation(View view) { Intent intent = new Intent(instance, SearchActivity.class); //创建一个rect 对象来存储共享元素的位置信息 Rect rect = new Rect(); //获取元素的位置信息 view.getGlobalVisibleRect(rect); //将位置信息附加到intent 上 intent.setSourceBounds(rect); CustomImage customImage = (CustomImage) view; intent.putExtra(ChooseCountry.EXTRA_SEARCH_SHAREIMAGE, customImage.getImageId()); startActivity(intent); //用于屏蔽 activity 默认的转场动画效果 overridePendingTransition(0, 0); }
其中,getGlobalVisibleRect() 方法的含义是,获取 可见的状态栏高度+可见的标题栏高度+Rect左上角到标题栏底部的距离,如果标题栏被隐藏了,那么可见标题栏高度为0。
接下来,就在在第二个界面接收位置信息并将该图片展示出来了。
3.模拟转场动画:
在第二个界面中,我们需要做如下的操作:
1.获取上共享元素信息。
2.计算共享元素缩放比例和位移距离。
3.调用动画,完成模拟转场效果。
4.隐藏搜索的图片,转变为可编辑的editText
/** * 初始化场景 */ private void initial() { // 获取上一个界面传入的信息 mRect = getIntent().getSourceBounds(); //图片资源 ID int mRescourceId = getIntent().getExtras().getInt(ChooseCountry.EXTRA_SEARCH_SHAREIMAGE); // 获取上一个界面中,图片的宽度和高度 mOriginWidth = mRect.right - mRect.left; mOriginHeight = mRect.bottom - mRect.top; // 设置 ImageView 的位置,使其和上一个界面中图片的位置重合 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mOriginWidth, mOriginHeight); params.setMargins(mRect.left, mRect.top - getStatusBarHeight(), mRect.right, mRect.bottom); mImageView.setLayoutParams(params); // 设置 ImageView 的图片和缩放类型 mImageView.setImageResource(mRescourceId); mImageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); // 根据上一个界面传入的图片资源 ID,获取图片的 Bitmap 对象。 BitmapDrawable bitmapDrawable = (BitmapDrawable) getResources().getDrawable(mRescourceId); Bitmap bitmap = bitmapDrawable.getBitmap(); // 计算图片缩放比例和位移距离 getBundleInfo(bitmap); }/** * 计算图片缩放比例,以及位移距离 */ private void getBundleInfo(Bitmap bitmap) { // 计算图片缩放比例,并存储在 bundle 中 if (bitmap.getWidth() >= bitmap.getHeight()) { mScaleBundle.putFloat(SCALE_WIDTH, (float) mScreenWidth / mOriginWidth); mScaleBundle.putFloat(SCALE_HEIGHT, (float) bitmap.getHeight() / mOriginHeight); } else { mScaleBundle.putFloat(SCALE_WIDTH, (float) bitmap.getWidth() / mOriginWidth); mScaleBundle.putFloat(SCALE_HEIGHT, (float) mScreenHeight / mOriginHeight); } // 计算位移距离,并将数据存储到 bundle 中 mTransitionBundle.putFloat(TRANSITION_X, mScreenWidth / 2 - (mRect.left + (mRect.right - mRect.left) / 2));// mTransitionBundle.putFloat(TRANSITION_Y, mScreenHeight / 2 - (mRect.top + (mRect.bottom - mRect.top) / 2)); mTransitionBundle.putFloat(TRANSITION_Y, -(mRect.top-getStatusBarHeight())); }
我们要将 Rect.top 的值减去状态栏的高度,这样才是相对于屏幕的绝对位置。
入场以及退场动画
/** * 模拟入场动画 */ private void runEnterAnim() { mImageView.animate() .setInterpolator(DEFAULT_INTERPOLATOR) .setDuration(DURATION) .scaleX(mScaleBundle.getFloat(SCALE_WIDTH)) .scaleY(mScaleBundle.getFloat(SCALE_HEIGHT)) .translationX(mTransitionBundle.getFloat(TRANSITION_X)) .translationY(mTransitionBundle.getFloat(TRANSITION_Y)) .start(); mImageView.setVisibility(View.VISIBLE); //add 作用隐藏原来的图片,显示为可编辑的editText mHandler.sendEmptyMessageDelayed(MESSAGE_SHOW_EDIT,DURATION); mHandler.sendEmptyMessageDelayed(MESSAGE_SHOW_KEYBOARD,DURATION*2); } /** * 模拟退场动画 */ @SuppressWarnings("NewApi") private void runExitAnim() { //add searchLine.setVisibility(View.GONE); searchTop.setVisibility(View.GONE); mImageView.setVisibility(View.VISIBLE); mImageView.animate() .setInterpolator(DEFAULT_INTERPOLATOR) .setDuration(DURATION) .scaleX(1) .scaleY(1) .translationX(0) .translationY(0) .withEndAction(new Runnable() { @Override public void run() { finish(); overridePendingTransition(0, 0); } }) .start(); }private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case MESSAGE_SHOW_KEYBOARD: CommonUtil.showKeyboard(instance, searchEdit); break; case MESSAGE_SHOW_EDIT: mImageView.setVisibility(View.GONE); searchTop.setVisibility(View.VISIBLE); searchLine.setVisibility(View.VISIBLE); searchEdit.requestFocus(); break; } } };
4.完整代码
4.1 界面一:
xml
"@+id/search_total_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:src="@drawable/search_totla_view" android:scaleType="centerInside"/>
activity
该id的点击事件调用的方法如下,获取共享元素的位置信息
private void showShareAnimation(View view) { Intent intent = new Intent(instance, SearchActivity.class); //创建一个rect 对象来存储共享元素的位置信息 Rect rect = new Rect(); //获取元素的位置信息 view.getGlobalVisibleRect(rect); //将位置信息附加到intent 上 intent.setSourceBounds(rect); CustomImage customImage = (CustomImage) view; intent.putExtra(ChooseCountry.EXTRA_SEARCH_SHAREIMAGE, customImage.getImageId()); startActivity(intent); //用于屏蔽 activity 默认的转场动画效果 overridePendingTransition(0, 0); }
4.2 界面二:
xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:background="@color/common_view_bg"> <com.accounttools.app.views.customviews.CustomImage android:id="@+id/activity_search_img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="centerInside" android:visibility="invisible"/> <RelativeLayout android:id="@+id/activity_search_top" android:layout_width="match_parent" android:layout_height="45dp" android:background="@color/status_bar_color" android:orientation="horizontal" android:paddingLeft="10dp" android:paddingRight="10dp" android:visibility="gone"> <LinearLayout android:id="@+id/search_top_cancel" android:layout_width="50dp" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" android:layout_alignParentRight="true"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" android:textColor="@color/common_red" android:text="@string/cancel"/> LinearLayout> <LinearLayout android:layout_toLeftOf="@id/search_top_cancel" android:layout_marginRight="10dp" android:layout_width="match_parent" android:layout_height="28dp" android:orientation="horizontal" android:paddingLeft="8dp" android:paddingRight="8dp" android:background="@drawable/drawable_search_layout" android:layout_centerVertical="true" android:gravity="center_vertical"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/search_icon"/> <EditText android:id="@+id/search_content" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="5dp" android:background="@color/transparent" android:textSize="15sp" android:hint="@string/search" android:textCursorDrawable="@drawable/drawable_search_cursor"/> LinearLayout> RelativeLayout> <TextView android:id="@+id/activity_search_line" android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/common_line_color" android:visibility="gone"/>LinearLayout>
activity
handler 的作用是当第二个界面显示动画结束后,隐藏imageView,显示可编辑的editText
/** * 搜索界面 * Created by lh on 2016/11/3. */public class SearchActivity extends BaseActivity { private static final int MESSAGE_SHOW_KEYBOARD = 1; private static final int MESSAGE_SHOW_EDIT = 2; public static final int DURATION = 300; private static final AccelerateDecelerateInterpolator DEFAULT_INTERPOLATOR = new AccelerateDecelerateInterpolator(); private static final String SCALE_WIDTH = "SCALE_WIDTH"; private static final String SCALE_HEIGHT = "SCALE_HEIGHT"; private static final String TRANSITION_X = "TRANSITION_X"; private static final String TRANSITION_Y = "TRANSITION_Y"; private Activity instance = SearchActivity.this; /** * 存储图片缩放比例和位移距离 */ private Bundle mScaleBundle = new Bundle(); private Bundle mTransitionBundle = new Bundle(); /** * 屏幕宽度和高度 */ private int mScreenWidth; private int mScreenHeight; /** * 上一个界面图片的宽度和高度 */ private int mOriginWidth; private int mOriginHeight; /** * 上一个界面图片的位置信息 */ private Rect mRect; private CustomImage mImageView; private EditText searchEdit; private RelativeLayout searchTop; private TextView searchLine; @Override public void onBackPressed() { // 使用退场动画 runExitAnim(); } @Override protected int getLayoutResId() { return R.layout.activity_search_layout; } @Override protected void initView() { // 获得屏幕尺寸 getScreenSize(); // 初始化界面 mImageView = (CustomImage) findViewById(R.id.activity_search_img); searchEdit = (EditText)findViewById(R.id.search_content); searchTop = (RelativeLayout)findViewById(R.id.activity_search_top); searchLine = (TextView)findViewById(R.id.activity_search_line); // 初始化场景 initial(); // 设置入场动画 runEnterAnim(); //动态显示搜索结果 showSearchResult(); } private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case MESSAGE_SHOW_KEYBOARD: CommonUtil.showKeyboard(instance, searchEdit); break; case MESSAGE_SHOW_EDIT: mImageView.setVisibility(View.GONE); searchTop.setVisibility(View.VISIBLE); searchLine.setVisibility(View.VISIBLE); searchEdit.requestFocus(); break; } } }; /** * 初始化场景 */ private void initial() { // 获取上一个界面传入的信息 mRect = getIntent().getSourceBounds(); //图片资源 ID int mRescourceId = getIntent().getExtras().getInt(ChooseCountry.EXTRA_SEARCH_SHAREIMAGE); // 获取上一个界面中,图片的宽度和高度 mOriginWidth = mRect.right - mRect.left; mOriginHeight = mRect.bottom - mRect.top; // 设置 ImageView 的位置,使其和上一个界面中图片的位置重合 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mOriginWidth, mOriginHeight); params.setMargins(mRect.left, mRect.top - getStatusBarHeight(), mRect.right, mRect.bottom); mImageView.setLayoutParams(params); // 设置 ImageView 的图片和缩放类型 mImageView.setImageResource(mRescourceId); mImageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); // 根据上一个界面传入的图片资源 ID,获取图片的 Bitmap 对象。 BitmapDrawable bitmapDrawable = (BitmapDrawable) getResources().getDrawable(mRescourceId); Bitmap bitmap = bitmapDrawable.getBitmap(); // 计算图片缩放比例和位移距离 getBundleInfo(bitmap); } /** * 计算图片缩放比例,以及位移距离 */ private void getBundleInfo(Bitmap bitmap) { // 计算图片缩放比例,并存储在 bundle 中 if (bitmap.getWidth() >= bitmap.getHeight()) { mScaleBundle.putFloat(SCALE_WIDTH, (float) mScreenWidth / mOriginWidth); mScaleBundle.putFloat(SCALE_HEIGHT, (float) bitmap.getHeight() / mOriginHeight); } else { mScaleBundle.putFloat(SCALE_WIDTH, (float) bitmap.getWidth() / mOriginWidth); mScaleBundle.putFloat(SCALE_HEIGHT, (float) mScreenHeight / mOriginHeight); } // 计算位移距离,并将数据存储到 bundle 中 mTransitionBundle.putFloat(TRANSITION_X, mScreenWidth / 2 - (mRect.left + (mRect.right - mRect.left) / 2));// mTransitionBundle.putFloat(TRANSITION_Y, mScreenHeight / 2 - (mRect.top + (mRect.bottom - mRect.top) / 2)); mTransitionBundle.putFloat(TRANSITION_Y, -(mRect.top-getStatusBarHeight())); } /** * 模拟入场动画 */ private void runEnterAnim() { mImageView.animate() .setInterpolator(DEFAULT_INTERPOLATOR) .setDuration(DURATION) .scaleX(mScaleBundle.getFloat(SCALE_WIDTH)) .scaleY(mScaleBundle.getFloat(SCALE_HEIGHT)) .translationX(mTransitionBundle.getFloat(TRANSITION_X)) .translationY(mTransitionBundle.getFloat(TRANSITION_Y)) .start(); mImageView.setVisibility(View.VISIBLE); //add mHandler.sendEmptyMessageDelayed(MESSAGE_SHOW_EDIT,DURATION); mHandler.sendEmptyMessageDelayed(MESSAGE_SHOW_KEYBOARD,DURATION*2); } /** * 模拟退场动画 */ @SuppressWarnings("NewApi") private void runExitAnim() { //add searchLine.setVisibility(View.GONE); searchTop.setVisibility(View.GONE); mImageView.setVisibility(View.VISIBLE); mImageView.animate() .setInterpolator(DEFAULT_INTERPOLATOR) .setDuration(DURATION) .scaleX(1) .scaleY(1) .translationX(0) .translationY(0) .withEndAction(new Runnable() { @Override public void run() { finish(); overridePendingTransition(0, 0); } }) .start(); } /** * 获取屏幕尺寸 */ private void getScreenSize() { Display display = getWindowManager().getDefaultDisplay(); Point size = new Point(); display.getSize(size); mScreenWidth = size.x; mScreenHeight = size.y; } /** * 获取状态栏高度 */ private int getStatusBarHeight() { //获取status_bar_height资源的ID int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { //根据资源ID获取响应的尺寸值 return getResources().getDimensionPixelSize(resourceId); } return -1; } private void showSearchResult(){ searchEdit.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { //搜索的匹配算法 Log.d("SearchActivity"," afterTextChanged 调用了 s="+s.toString()); } }); }}
5.参考资料:
1.Android 中的转场动画及兼容处理
2.https://github.com/wl9739/UITransitionDemo
3.用 Transition 完成 Fragment 共享元素的切换
https://github.com/hehonghui/android-tech-frontier/blob/master/issue-35/%E7%94%A8Transition%E5%AE%8C%E6%88%90Fragment%E5%85%B1%E4%BA%AB%E5%85%83%E7%B4%A0%E7%9A%84%E5%88%87%E6%8D%A2.md
更多相关文章
- android studio 适配android7.0 android 6.0拍照调用系统裁剪工
- Android(安卓)用户界面---操作栏(Action Bar 五)
- android 自定义ScrollView实现背景图片伸缩的实现代码及思路
- Android实现一个天气界面竟然如此简单?
- Android图片滚动,加入自动播放功能,使用自定义属性实现,霸气十足!
- Android(安卓)系统拍照及打开系统相册 完美适配 Android(安卓)4
- Android短信会话(查看会话记录以及会话详情界面)---短信管家3
- Android图文混排(一)-实现EditText图文混合插入上传
- 做了个拼图游戏