1. 程式人生 > >android 仿 ios 搜尋介面跳轉效果

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

<com.accounttools.app.views.customviews.CustomImage
            android:id="@+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.參考資料:

3.用 Transition 完成 Fragment 共享元素的切換