Android custom control Series 6: Custom ViewGroup (1) ViewPager effect, viewgroupviewpager

Source: Internet
Author: User

Android custom control Series 6: Custom ViewGroup (1) ViewPager effect, viewgroupviewpager

Today, we start a new Android custom component journey. The following content describes how to customize a ViewGoup. We have learned about the basic syntax of custom view through several blog posts, if you have any questions, refer to the following topic: Android custom controls.

This is also an example to illustrate how to customize a ViewGroup. The final goal is to implement a ViewGroup class of ViewPager function.


Let's take a look at the final effect:




We are very familiar with the ViewGroup of the system. The most commonly used LinearLayout and RelativeLayout have to deal with each other almost every day. Let's take a look at how to implement them step by step:


I. First, of course, it is also the most common process to create an Android project. Then, create a class in it that inherits from ViewGroup. Here we call this class MyViewPager.

After the ViewGroup is inherited, we will find that we need to implement the unimplemented method: the first, of course, is the constructor of two parameters, which is used to generate the corresponding component object in the xml layout file; the second is onLayout. We should be familiar with this method. As mentioned in the previous custom views, the onLayout method is used to specify the component location, in fact, the position is determined for the sub-View of the ViewGroup. Next, let's take a closer look at the location relationship that our ViewGroup wants to implement:

Add that We have 6 images to display. To achieve the viewpager effect, we need to display one image at a time and then slide between the left and right sides, other images before and after display (if there are images before and after display); To achieve this effect, in the onLayout method, how should we arrange our sub-views (for images, it is an ImageView object)? Let's look at the following figure and we will understand:



As shown in, in the onLayout method, the coordinate origin of the Android system is located at the vertex position in the upper left corner of the mobile phone screen, and the positive direction of x and y is to the right and down, respectively, for ViewPager effects that require horizontal sliding, our sub-View (ImageView) should be arranged horizontally in the x-y coordinate system as shown in the figure above, in this way, the pictures on the left and right can be displayed on the mobile phone screen only when the fingers slide horizontally. So here, what we need to do is:


1. obtain all the child views.
2. Sort these sub-views horizontally in the order they are displayed. the horizontal distance between the upper left corner of each sub-view and the upper left corner of the sub-view is different from the width of the mobile phone screen, all child views in the vertical direction are aligned.


The Code is as follows:

@ Override/*** arrange the layout of the Child view and determine the position of the child view. * @ param changed indicates whether the current layout has changed when the onLayout method is called, if it changes, it is true. Otherwise, false * @ param int l, int t, int r, int B indicates that the current view is our myviewpager, relative to the location of its parent view, here we are arranging our own child views * these parameters are basically useless */protected void onLayout (boolean changed, int l, int t, int r, int B) {// ① first get all the child viewfor (int I = 0; I <getChildCount (); I ++) {View view = getChildAt (I); // obtain the subview whose table is I. // ② arrange the subview. In fact, you need to call the layout method of the subview, the four parameters correspond to the left, top, right, and bottom views respectively. layout (I * getWidth (), 0, getWidth () * (I + 1), getHeight ());}}


View in the code above. the left, top, right, and bottom parameters in the layout method correspond to the left, top, right, and bottom up four sides of the current sub-view, corresponding to the parent ViewGroup, that is, the distance between our custom ViewGroup, so after the arrangement, the effect is not the effect, but the position where ImageView 0 is displayed on the screen of the mobile phone, other imageviews are arranged on the right.


2. Define the touch event so that we can use gestures to slide the subview we arranged in the onLayout method to display it.

Here we need to use the onTouchEvent method. This method is used to distribute the touch event through the dispatchTouchEvent method and process the onInterceptTouchEvent method. Then, the touch event is first transmitted to the onTouchEvent method, the transfer mechanism of touch events will be introduced in the next article. According to our previous practice, the switch statement is used in onTouchEvent to determine whether to perform different processing in three states: ACTION_DOWN, ACTION_MOVE, and ACTION_UP; here, we first use a class provided by Android for processing:GestureDetector.GestureDetectorIt can be used to receive an event and process different types of events: we only need to simply useGestureDetector.OnTouchEvent (event); the key point below is to create thisGestureDetectorMethods To implement the object:


Detector = new GestureDetector (context, new OnGestureListener () {@ Override/*** callback when a finger is lifted */public boolean onSingleTapUp (MotionEvent e) {// TODO Auto-generated method stubreturn false;} @ Override/*** when a finger clicks the screen */public void onShowPress (MotionEvent e) {// TODO Auto-generated method stub} @ Override/*** calls back when a finger slides on the screen */public boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {return false;} @ Override/*** calls back when a finger is holding a long press on the screen */public void onLongPress (MotionEvent e) {// TODO Auto-generated method stub} @ Override/*** callback during fast sliding */public boolean onFling (MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {// TODO Auto-generated method stubreturn false;} @ Override/*** the callback method when you press */public boolean onDown (MotionEvent e) {// TODO Auto-generated method stubreturn false ;}});



It is not hard to see that GestureDetector has many methods for event classification parsing... Here we only care about slide, so we only need to pay attention to the onScroll method: public boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY), four parameters, e1 indicates the ACTION_DOWN Event that first triggers this series of Event Events, and e2 indicates the ACTION_MOVE Event that triggers this Event, distanceX and distanceY indicate the sliding distance between the last onScroll call and the current onScroll call in the x and y directions. It is worth noting that the positive and negative values of distanceX and distanceY are not as positive as the coordinates displayed in the previous ViewGroup, but are positive for the left sliding value distanceX and negative for the right sliding value distanceX.


After figuring out the meaning of the parameter, what we need to do is to slide the sub-View in onScroll ~ Here we will introduce a method: public void scrollBy (int x, int y). Here we call this method to pass distanceX in, the second parameter is set to 0 (because you do not need to slide in the y direction) to achieve the objective of sliding: scrollBy (int) distanceX, 0); inside the scrollBy method, the scrollTo method is actually rewritten. The scrollTo method is used to move the reference point of the current view that calls the control to a coordinate point. Here is our ViewGroup, if the x coordinate of the reference point is moved to the negative screen width, will the actual sub-view be changed to the first one:


@ Override/*** call back when a finger slides on the screen */public boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {// System. out. println ("" + distanceX); // The method used to move the screen to the right when distanceX is positive and left when it is negative, this method calls the onScrollChanged method, and refreshes the view/*** dx to indicate the moving distance in the x direction, and dy to indicate the moving distance in the y direction. If you move up to the positive coordinate axis, the value is a positive value; otherwise, it is negative * // The scrollBy method is actually rewritten inside the scrollTo method, scrollTo is to move the reference point of the current view to a coordinate point scrollBy (int) distanceX, 0); return false ;}



Now, let's take a look at the effect. We will first write our custom ViewGroup in the layout file:


<com.example.myviewpager.ui.MyViewPager        android:id="@+id/my_view_pager"        android:layout_width="fill_parent"        android:layout_height="fill_parent" />



Then, in MainActivity, create the MyViewPager object myViewPager, put the images with resource files in the drawable directory, and call myViewPager cyclically. addview method to add a subview to our ViewGroup:


Package com. example. myviewpager; import android. app. activity; import android. OS. bundle; import android. view. window; import android. widget. imageView; import com. example. myviewpager. ui. myViewPager; public class MainActivity extends Activity {private MyViewPager myViewPager; // image resource id array private int [] ids = new int [] {R. drawable. a1, R. drawable. a2, R. drawable. a3, R. drawable. a4, R. drawable. a5, R. drawable. a6}; @ Overrideprotected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); // implement untitled requestWindowFeature (Window. FEATURE_NO_TITLE); setContentView (R. layout. activity_main); myViewPager = (MyViewPager) findViewById (R. id. my_view_pager); for (int I = 0; I <ids. length; I ++) {ImageView imageView = new ImageView (this); imageView. setBackgroundResource (ids [I]); // Add ImageView to custom viewgroupmyViewPager. addView (imageView );}}}

Now, let's take a look at the effect of the phase ~ ^ _ ^:



It looks pretty good. The basic slide function has been implemented, but there are still some problems:

1. continue to slide out when the first and last images are displayed. The blank content will be displayed and you will stay

2. When you raise your finger, it will not automatically restore to display a complete picture status.


3. solve two problems: display on a blank page, and automatically restore by raising your fingers. Select to display the complete picture.
1. Blank page display problems

The solution is to use an integer value, currentId, to indicate the position to which the current slide is. The position is 0 at the beginning. If the page slides to the right, when the condition for sliding to the next page is reached, currentId will be + 1. If you want to slide to the previous page, currentId will be-1. Of course, the value of currentId is also within a range, that is [0, getChildCount ()-1], if currentId <0, we let it be equal to 0, if currentId> = getChildCount, we let it be equal to getChildCount-1, then slide to the corresponding currentId position to stop, this solves the problem.


2. When you raise your finger, the image will be automatically restored.

The solution is to slide the distance between the finger and the start when the finger is pressed. If the distance is greater than 1/2 of the screen distance to the right or left, slide to the right or left to display an image, if it is less than 1/2, the page is displayed. The following two actions are involved: pressing and lifting. we can add the switch judgment condition in the onTouchEvent method of MyViewPager to obtain the corresponding value to determine the sliding condition, the main thing is to add content in the onTouchEvent method. Let's take a look:

/*** X coordinate of the down event */private int firstX; private int currentId; // id of the currently displayed image, starting from 0, and the maximum value is getChildCount () -1 @ Overridepublic boolean onTouchEvent (MotionEvent event) {super. onTouchEvent (event); // use a tool to parse the touch event detector. onTouchEvent (event); switch (event. getAction () {case MotionEvent. ACTION_DOWN: firstX = (int) event. getX (); break; case MotionEvent. ACTION_MOVE: break; case MotionEvent. ACTION_UP: int tmpId = 0; System. o Ut. println ("currentId =" + currentId); // slide your finger to the right more than 1/2 of the screen. The current id should be-1if (event. getX ()-firstX> getWidth ()/2) {tmpId = currentId-1;} else if (firstX-event. getX ()> getWidth ()/2) {tmpId = currentId + 1;} else {tmpId = currentId;} System. out. println ("currentId =" + currentId); // The efficiency of the Three-object operator is much higher than that of the if else operator. int childCount = getChildCount (); currentId = tmpId <0? 0: (tmpId> childCount-1 )? ChildCount-1: tmpId); scrollTo (currentId * getWidth (), 0); break; default: break;} // return true if this event is consumed ;}


After the above modification, let's take a look at the effect:


After the above changes, I found that the previous two problems have been solved, but it still seems a bit uncomfortable, isn't it... Just a moment later, it will pop up, and the user experience is not very good. So here we need to add a sliding delay mechanism after the loose hand, let it slide slowly multiple times, and finally slide to the position we want.


IV. Implementation of loose sliding delay:

For this part, Android actually has APIs available for use. Here we will first implement it on our own.

The core idea here is to call the scrollTo method, just to get the sliding distance after the finger is lifted, and then slide the distance at a constant speed within the specified time, therefore, we need to call the scrollTo method multiple times to move to different locations at different time points.


We write a class DistanceProvider to implement the above functions. We first get the distance to be moved, and then pass the distance parameter to startScroll, the start slide method of DistanceProvider.


First, comment out the previous scrollTo (currentId * getWidth (), 0); then write a method: moveToDest ();


/*** Method for moving to a reasonable position */private void moveToDest () {/*** distance from the start point to the end point */int distance = currentId * getWidth () -getScrollX ();/*** in a period of time, the distance is smoothly moved * // The calculation offset distanceProvider is enabled. startScroll (getScrollX (), 0, distance, 0); invalidate (); // invalidate will cause execution of ondraw and computeScroll Methods}




Next we will focus on how to write DistanceProvider. Here we will involve a knowledge point, that is, in the invalidate method, a method computeScroll will be called in addition to the onDraw method, this method is not implemented in the parent class. It is actually provided for developers. We can rewrite the computeScroll method to determine whether the automatic slide ends. If it is not completed, obtain the latest Slide Position and refresh the view again to call the invalidate method. Then, the invalidate method called this time will call the computeScroll method until the automatic slide is completed.



The DistanceProviderl class has the following implementation:


Package com. example. myviewpager. utils; import android. content. context; import android. OS. systemClock;/*** tool class for calculating the displacement distance ** @ author Alex **/public class DistanceProvider {private int startX; private int startY; private int distanceX; private int distanceY; /*** start animation execution time */private long startTime;/*** determines whether the animation is still being executed. true indicates that the animation has been stopped, false indicates that the operation is still running */private boolean isFinish;/*** default running time, in milliseconds, 300 milliseconds */private long duration; /*** current x value */private long currentX; private long currentY; public long getCurrentX () {return currentX;} public void setCurrentX (long currentX) {this. currentX = currentX;} public DistanceProvider (Context context) {isFinish = false ;} /***** start to move ** @ param startX * x start coordinate * @ param startY * y start coordinate * @ param distanceX * x distance to move * @ param distanceY * distance to be moved in the y direction */public void startScroll (int startX, int startY, int distanceX, int distanceY) {this. startX = startX; this. startY = startY; this. distanceX = distanceX; this. distanceY = distanceY; this. startTime = SystemClock. uptimeMillis (); // here we set the sliding duration to 300 ms this. duration= 300; this. isFinish = false;}/*** calculate the current running status ** @ return true: indicates that the running is finished; false: indicates that the operation is still running */public boolean computescroloffset () {if (isFinish) {return isFinish;} // calculate how long it took to slide and run long passTime = SystemClock. uptimeMillis ()-startTime; if (passTime <duration) {currentX = startX + distanceX * passTime/duration; currentY = startY + distanceX * passTime/duration ;} else {currentX = startX + distanceX; currentY = startY + distanceY; isFinish = true ;}return false ;}}




For the computeScroll method in the Custom viewgroup:


@ Overridepublic void computeScroll () {// super. computeScroll (); // calculates the sliding offset if (! DistanceProvider. computescroloffset () {int newX = (int) distanceProvider. getCurrentX (); scrollTo (newX, 0); // refresh invalidate ();}}



After this change, we basically implemented the ViewGroup function.



The next article will be based on this article.Android touch event Transmission Mechanism, So stay tuned! Thank you!


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.