Android custom controls summary

Custom control classification:

1. Use system controls to achieve custom effects

2. Define a class to inherit the View, such as textView and ImageView, and override the relevant methods to achieve new effects

3. Define a class to inherit ViewGroup and achieve the corresponding effect

Inherit the view class or viewgroup class to create the required controls. Generally speaking, it is easier to define controls by inheriting existing controls.

This paper introduces the basic process of implementing a user-defined view

1. Clarify the needs and determine the effect you want to achieve.

2. Determine whether to use the form of composite control or a new custom form. Composite control is to use multiple system controls to synthesize a new control, such as titlebar. This form is relatively simple.

3. If you want to completely customize a view, you first need to consider which class to inherit, view or subclasses such as ImageView.

4. Copy the View#onDraw, View#onMeasure and View#onLayout methods as needed.

5. Copy the dispatchTouchEvent and onTouchEvent methods as needed.

6. Provide custom attributes for your custom view as needed, that is, write attr XML, and then get the custom attribute value in the code through classes such as TypedArray.

7. Problems such as sliding conflict and pixel conversion need to be handled.

Drawing process

onMeasure measures the size of the view and sets the width and height of the view displayed on the screen.

onLayout determines the location of the view. The parent view will comprehensively determine the location of the child view (determine its size) according to the needs of the child view and its own situation.

onDraw(Canvas) draws the contents of the view.

Get the view in the main thread and call the invalid () method to refresh the current view, resulting in onDraw execution. If postvalidate is used in the child thread, or postvalidatedelayed (300) is not required to be refreshed all the time, refresh every 300 milliseconds.

If you want the drawing process of the view (three steps) to go through again completely, you can't use the invalidate() method, but call requestLayout().

Event conflict

When the click event occurs, the event is first passed to the Activity. The Activity will first process the event by the Window to which it belongs, that is, call the superDispatchTouchEvent() method.

By observing the call chain of the superDispatchTouchEvent() method, we can find the transmission order of events:

  • PhoneWinodw.superDispatchTouchEvent()
  • DecorView.dispatchTouchEvent(event)
  • ViewGroup.dispatchTouchEvent(event)

Events are passed to the ViewGroup layer by layer.

When an event occurs, it is first transmitted from the top level to the bottom level. For each sub view, see whether its onInterceptTouchEvent method is intercepted and whether ontouch consumes the method. If it does not continue to distribute the event downward, the upload will not be processed. When it returns to the top level, if the top level (activity) does not process the event, This event is equivalent to disappearing (no effect).

View has no onInterceptTouchEvent() method, but once a click event is passed to it, its ouTouchEvent() method will be called.

When a conflict is found in an event, the principle of handling is the event distribution mechanism. There are two methods:

  1. For external processing, override the onInterceptTouchEvent of the parent view. All MotionEvent events return false and are not intercepted;
  2. Internal processing. Rewrite the dispatchTouchEvent of the child view and request the parent control not to intercept its own events through the requestDisallowInterceptTouchEvent method (this method can interfere with the event distribution process of the parent element in the child element). true is not to intercept and false is to intercept.

What is the difference between Activity/Window/View? How is the Activity displayed on the screen

Activity manager: used to maintain and manage the startup and destruction of activities

Window managerservice: used to create, manage, and destroy windows.

Activity is like a craftsman (control unit), Window is like a Window (bearing model), View is like a Window flower (display View), layoutinflator is like scissors, and Xml configuration is like a Window flower drawing.

  • After the setContentView method is executed in the Activity, the setContentView of PhoneWindow will be executed. In this method, the DecorView component will be generated as the top-level view of the application window.
  • DecorView is an internal class of PhoneWindow and inherits from FrameLayout. DecorView will add a FrameLayout with id of content as the root layout, and the xml file of Activity will be parsed into a View tree through the insert method of layouteinflator and added to the FrameLayout with id of content.
  • ViewRoot is not a View, its implementation class is viewrootimpl, and ViewRoot is the "manager" of DecorView. It is the link between DecorView and WindowManager.
  • After all, the "manager", so the drawing process of View starts from the performTraversals method of ViewRoot. Therefore, the performTraversals method calls performMeasure,performLayout and performDraw in turn. Then they go through three processes: measure, layout and draw, which are finally displayed in front of the user. When the user clicks the screen, the click event is transferred to the Window along with the Activity, and finally distributed and processed by ViewGroup/View.

How ActivityThread, Ams and Wms work

ActivityThread: runs on the main thread of the application process and responds to ActivityMananger, Service startup, suspension of Activity, broadcast reception and other messages.

Ams: uniformly schedule the Activity, memory management and process management of each application.

There are several important ways to customize controls:

1. Implement the construction method. (three construction methods)

The second is the constructor called to create the layout file

2. onMeasure measures the size of the view. Set the width and height you want to display on the screen.

MeasureSpec has two properties: SpecMode and SpecSize. For an ordinary view, its MeasureSpec is determined by the MeasureSpec of the parent container and its own layoutparams. Therefore, for different parent containers and different layoutparams, views can have many different MeasureSpec.

SpecMode has three types.

unspecified: the parent View does not impose any restrictions on the child View. It is generally not concerned with this mode

Exactly: the size of the view is the size specified by SpecSize. Equivalent to mach_parents and specific values

at_most: the parent container specifies a specsize, and the view cannot be greater than this value. For specific values, see view, which is equivalent to wrap_content

LayoutParams is not the MeasureSpec that we contact most in daily development. During View measurement, LayoutParams will be combined with the MeasureSpec of the parent View and converted into the MeasureSpec of the View, so as to determine the size of the View.

  • For a top-level View (DecorView), its MeasureSpec is determined by the size of the window and its own LayoutParams.
  • For ordinary views, the MeasureSpec is determined by the parent container's Measure and its own LayoutParams.

Override onMeasure to measure the size of the view and set its width and height on the screen.

If you write a custom View that directly inherits the View and write Super Measure (), a measurement width and height will be set for the View by default. What is the width and height?

 //If the View has no background, the value of android:minWidth property is returned, which can be 0
 //If the View has a background set, the maximum of android:minWidth and the minimum background width is returned.

If you write a custom View that inherits the existing control and write Super Measure (), the measured width and height of the existing control will be used by default. You can modify the measured width and height. Of course, you can also re measure it all and then change it.

If our View directly inherits ImageView, ImageView has run a lot of written code and measured the corresponding width and height. We can change it based on it. For example, our Image2View is a custom square ImageView:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    //Here, we have measured the width and height under the rules of ImageView, and assigned it through the setMeasuredDimension method.
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    //Here we use getMeasuredWidth/Height to get the measured width and height that have been assigned
    //Then help us measure the width and height in ImageView, and take the small value as the edge of the square.
    //Then call setMeasuredDimension again to override the assignment of ImageView.
    //We didn't carry out complex measurement from scratch. It all depends on ImageView. ha-ha
    int width = getMeasuredWidth();
    int height = getMeasuredHeight();
    if (width < height) {
        setMeasuredDimension(width, width);
    } else {
        setMeasuredDimension(height, height);
    }
}
  • Only after setMeasuredDimension can we get the width and height of measure. This step is done in super because this method is used to set the width and height measured by view.
  • If you need to re measure or dynamically change the size of the custom control, you need to rewrite the rule makeMeasureSpec according to your needs. In short, you need to rewrite the rule when the rule changes.
  • The onMeasure method is rewritten to give the view a warp_ The default size under the content attribute. If onMeasure is not overridden, the system does not know how large the default size should be. If not, wrap_content is equivalent to match_parent. Therefore, custom controls need to support warp_ The content attribute overrides onMeasure. How to rewrite it?
You can try to customize one yourself View,Then don't rewrite onMeasure()Method, you will find that only settings match_parent and wrap_content The effect is the same, in fact TextView,ImageView And so on wrap_content Have their own treatment on the.   @Override   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {       super.onMeasure(widthMeasureSpec, heightMeasureSpec);       Log.d(TAG, "widthMeasureSpec = " + widthMeasureSpec + " heightMeasureSpec = " + heightMeasureSpec);        //Specify a set of default width and height. The specific value depends on what you want in wrap_ In cotent mode / / how big should the size of the control be set? Int mwidth = 200; int mHeight = 200;         int widthSpecMode = MeasureSpec. getMode(widthMeasureSpec);        int widthSpecSize = MeasureSpec. getSize(widthMeasureSpec);         int heightSpecMode = MeasureSpec. getMode(heightMeasureSpec);        int heightSpecSize = MeasureSpec. getSize(heightMeasureSpec);         if (widthSpecMode == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST) {           setMeasuredDimension(mWidth, mHeight);       }  else if (widthSpecMode == MeasureSpec.AT_MOST) {           setMeasuredDimension(mWidth, heightSpecSize);       }  else if (heightSpecMode == MeasureSpec.AT_MOST) {           setMeasuredDimension(widthSpecSize, mHeight);       }   }
 

3. onLayout sets the position displayed on the screen (only used in custom ViewGroup). This coordinate is relative to the parent view of the current view. The view itself has some suggestions, and the decision is in the hands of the parent view.

Call scene: called when the view needs to set the size and position for its children. Child views, including children, must override the onLayout(boolean, int, int, int, int) method and call their respective layout(int, int, int, int) methods.

	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		
		for (int i = 0; i < getChildCount(); i++) {
			View view = getChildAt(i); // Gets the child view with subscript I

			/**
			 * The parent view will comprehensively determine the location of the child view (determine its size) according to the needs of the child view and its own situation
			 */
			//Specify the position of the child view. Left, top, right and bottom refer to the position in the viewGround coordinate system
			view.layout(0+i*getWidth(), 0, getWidth()+i*getWidth(), getHeight());	

		}	
	}

4. onDraw(Canvas) draws the contents of the view. Controls how it appears on the screen (not required for customizing viewgroup s)

/*
* backgroundBitmap Picture to draw
* left Left border of picture
* top Upper boundary of picture
* paint The brush to use to draw the picture
*/
canvas.drawBitmap(backgroundBitmap, 0, 0, paint);

Differences between View and ViewGroup

  1. onLayout needs to be implemented when ViewGroup needs to control how child view s are placed.
  2. View has no child views, so onLayout method is not required. onDraw is implemented if necessary
  3. Inheriting the existing controls or containers in the system, such as FrameLayou, will help us implement onMeasure method. We don't need to implement onMeasure. If we inherit View or ViewGroup, we need warp_ The onMeasure method needs to be implemented for the content attribute
  4. The custom ViewGroup mostly controls how the sub views are placed and makes corresponding changes (sliding pages, switching pages, etc.). The custom view mainly draws some shapes through onDraw, and then determines how to change by touching events

scrollTo() and scrollBy()

scrollTo: move the datum point of the current view to a point (coordinate point);

ScrollBy moves the current view content a distance.

The difference between getHeight() and getMeasuredHeight():

There are two ways to get the width and height of a control

  • getMeasuredHeight(): the actual size of the control

Obtain the measured height. As long as the onmeasure method is executed, you can use it to obtain the width and height. Use the view in the user-defined view Measure (0,0) method can actively inform the system to measure, and then you can directly use it to obtain width and height. Onmeasure called in measure

  • getHeight(): the display size of the control can only be obtained after the onLayout method is executed. This method is not good. It can only be obtained after all measurements are completed. The obtained height is the height displayed on the screen, and getMeasuredHeight is the actual height.
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
					@Override
					public void onGlobalLayout() {
					headerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
					int headerViewHeight = headerView.getHeight();
					//Width and height can be obtained directly
			}
		});

The two general situations are the same, but in the viewgroup, getWidth is the space allocated by the parent class to the child view: right - left. The system may need multiple measurements to determine the final measured width and height, which may be inaccurate. It is a good habit to obtain the measured width and height or the final width and height in onlayout.

There is another way to get the width and height of the control:

onSizeChanged: this method is called back when the size of the component is changed

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		// Called when the size changes
		mHeight = getMeasuredHeight();
		mWidth = getMeasuredWidth();
		// Range of movement
		mRange = (int) (mWidth * 0.6f);
		
	}

onFinishInflate

Called when the xml is filled in. In the custom viewgroup, you can obtain the child view object through this method

protected void onFinishInflate() {
		super.onFinishInflate();
		// Fault tolerance check (there are at least two child views, and the child View must be a subclass of ViewGroup)
		if(getChildCount() < 2){
			throw new IllegalStateException("I have at least two children. Your ViewGroup must have 2 children at least.");
		}
		if(!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)){
			throw new IllegalArgumentException("son View Must be ViewGroup Subclass of. Your children must be an instance of ViewGroup");
		}
		
		mLeftContent = (ViewGroup) getChildAt(0);
		mMainContent = (ViewGroup) getChildAt(1);
	}

Other concepts

  • The position parameters of the view include left, right, top and bottom (which can be obtained by getXX). After 3.0, several parameters have been added: X, y, translationX and translationY, where x and y are the coordinates of the upper left corner of the view, and translationX and translationY are the offset of the upper left corner of the view relative to the parent container. These parameters are coordinates relative to the parent container, and the default values of translationX and translationY are 0. Their conversion relationship is: x = left + translationX y = Top + translationY. It should be noted that during view translation, top and left represent the position information of the original upper left corner, and their values will not change. At this time, the four parameters x, y, translationX and translationY will change
  • Touchslope is the minimum distance recognized by the system and considered as sliding. For example, when the sliding distance of two sliding events is less than this value, we can consider that the critical value of sliding distance is not reached

Event distribution

Execution order of onTouch, onTouchEvent and onClick of setOnTouchListener in View

Trace back to the dispatchTouchEvent source code of View. There is such a piece of code

public boolean dispatchTouchEvent(MotionEvent event) {  
        if (!onFilterTouchEventForSecurity(event)) {  
            return false;  
        }  

        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
                mOnTouchListener.onTouch(this, event)) {  
            return true;  
        }  
        return onTouchEvent(event);  
    }
}

When any of the following three conditions is not true,

  • Mnontouchlistener is not null
  • view is the state of enable
  • mOnTouchListener.onTouch(this, event) returns true,

The function executes to onTouchEvent. Here, we can see that the first execution is mnontouchlistener The ontouch method, followed by the onTouchEvent method

Continue to trace the source code to onTouchEvent() and find that it is processing action_ There is such a code in the up event

if (!post(mPerformClick)) {        performClick();    }

At this point, the onClick method is finally executed

So the order of the three is:

  1. onTouch of setOnTouchListener()
  2. onTouchEvent()
  3. onClick()

Event distribution of view: why does view have a dispatchTouchEvent method?

View can register many event listeners. The scheduling order of events is ontuchlistener > ontuchevent > onlongclicklistener > onclicklistener

Event distribution for View

When an event occurs, it is first passed down from the top-level parent class. For each child, see whether his onInterceptTouchEvent method is intercepted and whether ontouch consumes the method. If the event is not distributed down to dispatchTouchEvent, it will not be processed back to the top-level parent space. If the top-level activity does not process this event, This event is equivalent to disappearing (no effect).

In Touchevent, if the return value is true, the event is consumed. If the return value is false, the event is not consumed and will continue to be passed

1) dispatchTouchEvent: this method is used to distribute events. If an event is intercepted and handed over to ontouchevent for processing, it corresponds to the above and ontouch understanding. Otherwise, it is passed to the child view

2) onInterceptTouchEvent: this method is used to intercept events. Returning true means to intercept (events are not allowed to continue to be passed to child views), and false means not to intercept. If a child view in a custom viewgroup needs to handle events by itself, it is necessary to rewrite the method to return false.

3) onTouchEvent: this method is used to handle events. Android event distribution is delivered to ViewGroup first, and then to View by ViewGroup., If the passed events are consumed in the child View, the ViewGroup will not receive any events.

onTouchEvent

General custom controls need to override the onTouchEvent method.

1. Record coordinate points when down

getX/getY obtains the coordinates relative to the upper left corner of the current View, and getRawX/getRawY obtains the coordinates relative to the upper left corner of the screen.

For example, when touching a button, x and y are the relative positions relative to the upper left point of the button. Rawx and rawy are always relative to the screen.

2. Calculate the offset when moving, and use the scrollTo() or scrollBy() method to move the view. Both methods are fast sliding and move instantaneously.

Note: the scrolling is not the viewgroup content itself, but its rectangular border.

Three sliding methods

  1. Use scrollTo() or scrollBy()
  2. animation
  3. Change the layoutparams in real time and rearrange the layout

If you want the view to move to a certain position within a period of time (not fast sliding, elastic) method:

a. Use custom animation (let the view do something over a period of time), extend animation,

The translation X Y these two values

(distance moved relative to parent container)

b. Using scorer

c.offsetTopAndBottom(offset) and offsetLeftAndRight(offset);, This is easy to understand. How much does it move left and right

Template (fixed code):

	 * @param startX	At the beginning X coordinate
	 * @param startY	At the beginning Y coordinate
	 * @param disX		X Distance to move in direction
	 * @param disY		Y Distance to move in direction
myScroller.startScroll(getScrollX(),0,distance,0,Math.abs(distance));//Duration
/**
	 * Scroller Do not actively call this method
	 * And invalidate() can drop this method
	 * invalidate->draw->computeScroll
	 */
	@Override
	public void computeScroll() {
		super.computeScroll();
		if(scroller.computeScrollOffset()){//Returns true, indicating that the animation is not over
			scrollTo(scroller.getCurrX(), 0);
			invalidate();
		}
	}

Working principle of the scroller: the scroller itself cannot slide the view. It needs to cooperate with the comouteScroll method of the view to complete the effect of elastic sliding. It constantly redraws the view, and there will be a time interval between each redrawing and the sliding start time. Through this time interval, srcoller can get the current sliding position of the view, Knowing the sliding position, you can complete the sliding of the view through the scrollTo method. In this way, each redrawing of the view will lead to a small sliding of the view, and multiple small sliding forms an elastic animation.

3. When up, judge the page position to be displayed, calculate the distance and slide the page. See below:

ontouch touch events can also be implemented by other tool classes

1.GestureDetector (gesture recognizer) can handle fast sliding events in onfling and motionevent ACTION_ When there is no fast sliding in the up. Sometimes it is more convenient than control, such as handling onfling, onscroll (drag after pressing the screen), Chang'an, double clicking and so on.

mDectector.onTouchEvent(event);// Delegate gesture recognizer to handle touch events
...	
case MotionEvent.ACTION_UP:
			
			if(!isFling){//  Judge currid by position only when there is no fast sliding
				int nextId = 0;
				if(event.getX()-firstX>getWidth()/2){ // Slide your finger to the right and exceed 1 / 2 of the screen's current currid - 1
					nextId = currId-1;
				}else if(firstX - event.getX()>getWidth()/2){ // Slide your finger to the left, exceeding 1 / 2 of the screen and the current currid + 1
					nextId = currId+1;
				}else{
					nextId = currId;
				}	
				moveToDest(nextId);
//				scrollTo(0, 0);
			}
		
			isFling = false;
			
			break;
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
					float distanceY) {
				//Move screen 	
				/**
				 * Move the current view content a distance
				 * disX	 X Direction shift distance 		 If yes, the picture moves to the left; if negative, the picture moves to the right 
				 * disY  Y Distance moved in direction
				 */
				scrollBy((int) distanceX, 0);
		
				return false;
			}
			
			@Override
			/**
			 * Callback when fast sliding occurs
			 */
			public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
					float velocityY) {
				isFling = true;
				if(velocityX>0 && currId>0){ // Slide quickly to the right. Subscript of current subview
					currId--;
				}else if(velocityX<0 && currId<getChildCount()-1){ // Swipe left quickly
					currId++;
				}
				
				moveToDest(currId);
				
				return false;
			}
			
			@Override
			public boolean onDown(MotionEvent e) {
				return false;
			}
		});

2. Give it to ViewDragHelper for processing

Usage:

	// a. Initialization (through static methods) 
		mDragHelper = ViewDragHelper.create(this , mCallback);
	
// b. Pass touch event
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// Pass to mDragHelper
		return mDragHelper.shouldInterceptTouchEvent(ev);
	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		try {
			mDragHelper.processTouchEvent(event);
		} catch (Exception e) {
			e.printStackTrace();
		}
		// Return true and continue to accept events
		return true;
	}
ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
		// c. Rewrite event
		
		// 1. Determine whether the current child can be dragged according to the returned result
		// child View currently dragged
		// pointerId the id that distinguishes multi touch
		@Override
		public boolean tryCaptureView(View child, int pointerId) {
			Log.d(TAG, "tryCaptureView: " + child);
			return true;
		};
		
		@Override
		public void onViewCaptured(View capturedChild, int activePointerId) {
			Log.d(TAG, "onViewCaptured: " + capturedChild);
			// Called when capturedChild is captured
			super.onViewCaptured(capturedChild, activePointerId);
		}
 
		@Override
		public int getViewHorizontalDragRange(View child) {
			// Returns the drag range without any real restrictions Only determines the execution speed of the animation
			return mRange;
		}
		
		// 2. Correct the (lateral) position to be moved according to the recommended value (important)
		// There is no real movement at this time
		public int clampViewPositionHorizontal(View child, int left, int dx) {
			// child: currently dragged View
			// Recommended value of left new position, dx position change
			// left = oldLeft + dx;
			Log.d(TAG, "clampViewPositionHorizontal: " 
					+ "oldLeft: " + child.getLeft() + " dx: " + dx + " left: " +left);
			
			if(child == mMainContent){
				left = fixLeft(left);
			}
			return left;
		}
 
		// 3. When the View position changes, handle the things to be done (update the status, accompany the animation, redraw the interface)
		// At this point, the position of the View has changed
		@Override
		public void onViewPositionChanged(View changedView, int left, int top,
				int dx, int dy) {
			// changedView a View that changes position
			// Left new left value
			// dx horizontal variation
			super.onViewPositionChanged(changedView, left, top, dx, dy);
			Log.d(TAG, "onViewPositionChanged: " + "left: " + left + " dx: " + dx);
			
			int newLeft = left;
			if(changedView == mLeftContent){
				// Pass the current variation to mMainContent
				newLeft = mMainContent.getLeft() + dx;
			}
			
			// Make corrections
			newLeft = fixLeft(newLeft);
			
			if(changedView == mLeftContent) {
				// When the left panel is moved, it is forced back
				mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
				mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
			}
			// Update status, perform animation
			dispatchDragEvent(newLeft);
			
			// In order to be compatible with lower versions, redraw the values after each modification
			invalidate();
		}
 
		// 4. What to do when the View is released (execute animation)
		@Override
		public void onViewReleased(View releasedChild, float xvel, float yvel) {
			// View releasedChild released child view 
			// float xvel horizontal velocity, right+
			// float yvel vertical velocity, downward+
			Log.d(TAG, "onViewReleased: " + "xvel: " + xvel + " yvel: " + yvel);
			super.onViewReleased(releasedChild, xvel, yvel);
			
			// Judgment execution off / on
			// First consider all open cases, and the rest are closed cases
			if(xvel == 0 && mMainContent.getLeft() > mRange / 2.0f){
				open();
			}else if (xvel > 0) {
				open();
			}else {
				close();
			}
			
		}
 
		@Override
		public void onViewDragStateChanged(int state) {
			// TODO Auto-generated method stub
			super.onViewDragStateChanged(state);
		}
/**
	 * Correct the left value according to the range
	 * @param left
	 * @return
	 */
	private int fixLeft(int left) {
		if(left < 0){
			return 0;
		}else if (left > mRange) {
			return mRange;
		}
		return left;
	}

Accompanying animation can also be used when the view moves:

	private void animViews(float percent) {
		//		> 1.  Left panel: zoom animation, pan animation, transparency animation
					// Zoom animation 0.0 - > 1.0 > > > 0.5f - > 1.0F > > > 0.5f * percent + 0.5f
			//		mLeftContent.setScaleX(0.5f + 0.5f * percent);
			//		mLeftContent.setScaleY(0.5f + 0.5f * percent);
					ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f));
					ViewHelper.setScaleY(mLeftContent, 0.5f + 0.5f * percent);
					// Pan Animation: - mwidth / 2.0f - > 0.0F
					ViewHelper.setTranslationX(mLeftContent, evaluate(percent, -mWidth / 2.0f, 0));
					// Transparency: 0.5 - > 1.0F
					ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.5f, 1.0f));
				
		//		> 2.  Main panel: zoom animation
					// 1.0f -> 0.8f
					ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f));
					ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f));
					
		//		> 3.  Background animation: brightness change (color change)
					getBackground().setColorFilter((Integer)evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), Mode.SRC_OVER);

Added by thehippy on Wed, 12 Jan 2022 11:04:33 +0200