This article has authorized the wechat public account: Hongyang (hongyangAndroid) in the wechat public account platform original launch

A preface.

Last time WHEN I opened palm reading, I saw that the animation effect of books was not bad. Recently, I am also working on a reader project, so I want to implement it in the project.

2. Train of thought

Before talking about the idea, let’s have a look at the implementation:

After seeing the implementation effect, let’s talk about the implementation idea:

  1. To obtainRecyclerView(orGridView) in theThe ImageView inside the child ViewIn the screen position, because we’re getting the Window position, we have to subtract the Y position outHeight of the status bar.
  2. Book cover and content page (actually twoImageView) set to just fetchedThe ImageView inside the child ViewThe location and size of.
  3. Set the animation, the calculation of the axis point of the zooming animation needs to be paid attention to, and when the code is explained in detail, there is also the use ofCameraClass (unusual Camera class) implementation of the open and close animation (if you are not familiar with the Camera, recommend first read GcsSloop big guy this articleMatrix Camera).

Three. Concrete implementation

I’ll walk you through the process step by step how to achieve this effect: 1. Layout activity_open_book.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.activity.OpenBookActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycle"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <ImageView
        android:id="@+id/img_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        android:contentDescription="@string/app_name" />

    <ImageView
        android:id="@+id/img_first"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:scaleType="centerCrop"
        android:visibility="gone"
        android:contentDescription="@string/app_name" />

</RelativeLayout>
Copy the code

Recycler_item_book.xml: RecylerVIew sublayout, actually ImageView and TextView, not posted here.

2. Animation We only cover rotation animation, because rotation animation also involves scaling animation. If you think about it, if you want to scale in an interface, you have to find the pivot point, how do you calculate the x and y coordinates of the pivot point? To better figure out the coordinates, let’s first look at a picture:

x / pl = vr / pr
pl
vr
pr
pl = ml + x
vr = w - x
pr = pw -pl
x = ml * pw / (pw - w)

public class Rotate3DAnimation extends Animation {
    private static final String TAG = "Rotate3DAnimation";

    private final float mFromDegrees;
    private final float mToDegrees;
    private final float mMarginLeft;
    private final float mMarginTop;
    // private final float mDepthZ;
    private final floatmAnimationScale; private boolean reverse; private Camera mCamera; // Rotate center privatefloat mPivotX;
    private float mPivotY;

    private floatscale = 1; // <------- public Rotate3DAnimation(Context Context,float mFromDegrees, float mToDegrees, float mMarginLeft, float mMarginTop,
                             floatanimationScale, boolean reverse) { this.mFromDegrees = mFromDegrees; this.mToDegrees = mToDegrees; this.mMarginLeft = mMarginLeft; this.mMarginTop = mMarginTop; this.mAnimationScale = animationScale; this.reverse = reverse; Scale = context.getResources().getDisplayMetrics().density; } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); mCamera = new Camera(); mPivotX = calculatePivotX(mMarginLeft, parentWidth, width); mPivotY = calculatePivotY(mMarginTop, parentHeight, height); Log.i(TAG,"width:"+width+",height:"+height+",pw:"+parentWidth+",ph:"+parentHeight);
        Log.i(TAG,"Center point X :"+mPivotX+", center y:+mPivotY);
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);

        floatdegrees = reverse ? mToDegrees + (mFromDegrees - mToDegrees) * interpolatedTime : mFromDegrees + (mToDegrees - mFromDegrees) * interpolatedTime; Matrix matrix = t.getMatrix(); Camera camera = mCamera; camera.save(); camera.rotateY(degrees); camera.getMatrix(matrix); camera.restore(); // Fix distortion, mainly MPERSP_0 and MPERSP_1float[] mValues = new float[9]. matrix.getValues(mValues); MValues [6] = mValues[6] / scale; MValues [7] = mValues[7] / scale; // matrix. SetValues (mValues); // reassignif (reverse) {
            matrix.postScale(1 + (mAnimationScale - 1) * interpolatedTime, 1 + (mAnimationScale - 1) * interpolatedTime,
                    mPivotX - mMarginLeft, mPivotY - mMarginTop);
        } else{ matrix.postScale(1 + (mAnimationScale - 1) * (1 - interpolatedTime), 1 + (mAnimationScale - 1) * (1 - interpolatedTime), mPivotX - mMarginLeft, mPivotY - mMarginTop); ** @param marginLeft The distance to the left of the View from the parent layout * @param parentWidth width of the parent layout * @param width View width * @returnScale the abscissa of the center */ publicfloat calculatePivotX(float marginLeft, float parentWidth, float width) {
        returnparentWidth * marginLeft / (parentWidth - width); ** @param marginTop The distance between the top of the View and the top of the parent layout * @param parentHeight the height of the parent layout * @param height The height of the child layout * @returnThe vertical coordinate of the center of the scale */ publicfloat calculatePivotY(float marginTop, float parentHeight, float height) {
        return parentHeight * marginTop / (parentHeight - height);
    }

    public void reverse() {
        reverse = !reverse;
    }
}
Copy the code

Calculating the scaling point we have discussed above, we will just look at the function applyTransformation(float interpolatedTime, Transformation T), We first judge whether we are currently in the state of opening or closing the book (the two states make the animation opposite), calculate the current rotation degree and then obtain the Camera. The Camera. RotateY (degrees) is used to rotate the book around the Y-axis, and then get our matrix and scale around the calculated center point. 3. Using this step, we need to use animation to our interface. When clicking our RecyclerView, we need to take out the ImageView in RecyclerView sub-view, and use the listener in the adapter to send out:

public interface OnBookClickListener{
    void onItemClick(int pos,View view);
}
Copy the code

Next, we implement the OnBookClickListener interface in OpenBookActivity, omiting some code:

public class OpenBookActivity extends AppCompatActivity implements Animation.AnimationListener,BookAdapter.OnBookClickListener {
    private static final String TAG = "OpenBookActivity"; // a list of variables omitted here... Private int[] location = new int[2]; Private ImageView mContent; // Private ImageView mFirst; Private ContentScaleAnimation scaleAnimation; Private Rotate3DAnimation threeDAnimation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_open_book);

        initWidget();
    }

    private void initWidget() {... StatusHeight = -1; Int resourceId = getResources().getidentifier ()"status_bar_height"."dimen"."android");
        if(resourceId > 0) {statusHeight = getResources().getDimensionPixelSize(resourceId); } initData(); . } // Add data repeatedly private voidinitData() {
        for(int i = 0; i<10; i++){ values.add(R.drawable.preview); } } @Override protected voidonRestart() { super.onRestart(); // Close the book animation when the interface re-entersif(isOpenBook) {
            scaleAnimation.reverse();
            threeDAnimation.reverse();
            mFirst.clearAnimation();
            mFirst.startAnimation(threeDAnimation);
            mContent.clearAnimation();
            mContent.startAnimation(scaleAnimation);
        }
    }

    @Override
    public void onAnimationEnd(Animation animation) {
        if(scaleanimation.hasended () && threedanimation.hasended ()) {// Handle subsequent operations when both animations have endedif(! isOpenBook) { isOpenBook =true;
                BookSampleActivity.show(this);
            } else {
                isOpenBook = false; mFirst.clearAnimation(); mContent.clearAnimation(); mFirst.setVisibility(View.GONE); mContent.setVisibility(View.GONE); } } } @Override public void onItemClick(int pos,View view) { mFirst.setVisibility(View.VISIBLE); mContent.setVisibility(View.VISIBLE); View.getlocationinwindow (location); int width = view.getWidth(); int height = view.getHeight(); / / two ImageView set size and position of RelativeLayout. LayoutParams params = (RelativeLayout. LayoutParams) mFirst. GetLayoutParams (); params.leftMargin = location[0]; params.topMargin = location[1] - statusHeight; params.width = width; params.height = height; mFirst.setLayoutParams(params); mContent.setLayoutParams(params); Bitmap contentBitmap = bitmap.createBitMap (width,height, bitmap.config. ARGB_8888); contentBitmap.eraseColor(getResources().getColor(R.color.read_theme_yellow)); mContent.setImageBitmap(contentBitmap); . / / set cover Bitmap coverBitmap = BitmapFactory decodeResource (getResources (), values, the get (pos)); mFirst.setImageBitmap(coverBitmap); // Set the cover initAnimation(view); Log.i(TAG,"left:"+mFirst.getLeft()+"top:"+mFirst.getTop()); mContent.clearAnimation(); mContent.startAnimation(scaleAnimation); mFirst.clearAnimation(); mFirst.startAnimation(threeDAnimation); } private void initAnimation(View View) {float viewWidth = view.getWidth();
        float viewHeight = view.getHeight();

        DisplayMetrics displayMetrics = new DisplayMetrics();
        getWindow().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        float maxWidth = displayMetrics.widthPixels;
        float maxHeight = displayMetrics.heightPixels;
        float horScale = maxWidth / viewWidth;
        float verScale = maxHeight / viewHeight;
        float scale = horScale > verScale ? horScale : verScale;

        scaleAnimation = new ContentScaleAnimation(location[0], location[1], scale, false); scaleAnimation.setInterpolator(new DecelerateInterpolator()); / / set the interpolation, scaleAnimation setDuration (1000); scaleAnimation.setFillAfter(true); / / animation stay in the final frame scaleAnimation setAnimationListener (OpenBookActivity. This); threeDAnimation = new Rotate3DAnimation(OpenBookActivity.this, -180, 0 , location[0], location[1], scale,true); threeDAnimation.setDuration(1000); / / set the animation length threeDAnimation. SetFillAfter (true); / / keep the rotation effect threeDAnimation. SetInterpolator (new DecelerateInterpolator ()); }}Copy the code

The first focus is the onItemClick method in the clone OnBookClickListener, where:

  • We get it based onviewCalculates the position and size of the two ImageViews in the current interface.
  • Computes zoom parameters and the order in which the animation is played, unwinds the animation, and handles the event after the animation ends.

The second important point is that when the center returns to the current screen, close the animation of the book, that is, execute the animation backwards, in the onRestart() method, and hide the two ImageViews after execution.

4. To summarize

Generally speaking, it is the simple use of Camera and Animation. My level is limited, which is inevitable. Welcome to put forward. Project address: Test Over~

Quote: Matrix Camera