Let's start by looking at the Youku controls.
Just respond to the last click of the top card, if not the topmost card then put it to the top, and then move to the front, repeated as follows.
Now that we know this, we'll be fine.
The technical details inside may be the child's placement to the front of the animation problem.
Let's take a look at the results we've achieved:
Then carefully analyze how we want to achieve the effect:
I also placed a button and two view on top of the control, which responds to events only when the control is at the top and innermost.
And then we're going to implement this control.
We inherited a viewgroup and named Exchagecarldview, and the beginning of course was its onmeasure and OnLayout method. The code is posted here and explained.
@Overrideprotected void onmeasure (int widthmeasurespec, int heightmeasurespec) {Measurechildren (Widthmeasurespec, HEIGHTMEASURESPEC); Super.onmeasure (Widthmeasurespec, heightmeasurespec);}
@Overrideprotected void OnLayout (Boolean changed, int l, int t, int r, int b) {int count = Getchildcount (); if (MIsE xchageanimation)///animation path {for (int i = 0; i < count; i++) {if (Mtouchindex > I)//when the head above the click does not need to change layoutcontinue; View view = Getchildat (i);//cache The first view information is the beginning of the animation information cacheviewtopandbottomifneed (i, view); if (count-1 = = i)//topmost layout {// Calculate how far it should go. total height int total_dis = view.getheight ()/2 * (Count-1-Mtouchindex);//calculates the current linear distance int now_dis = (int) (Total_dis * (System.currenttimemillis ()-manimationstarttime)/default_animtion_time);//regression cannot exceed Total_dis this value int dis = math.min ( Now_dis, Total_dis); View.layout (View.getleft (), Mviewstopcache.get (i) + Dis, view.getright (), Mviewsbottomcache.get ( i) + dis);} else{//remove the top of that layout//each card should be moved view.height 1/2int Total_dis = View.getheight ()/2;//calculates the current linear distance int now_dis = (int) (total _dis * (System.currenttimemillis ()-manimationstarttime)/default_animtion_time);//regression cannot exceed Total_dis this value int dis = Math.min (Now_dis, Total_dis);//Place layoutPosition View.layout (View.getleft (), Mviewstopcache.get (i)-Dis, view.getright (), Mviewsbottomcache.get (i)-dis);} Detects if the animation is over Checkanimation ();}} else{//Initialize our card when initializing mtotalhight = 0;for (int i = 0; i < count; i++) {View view = Getchildat (i); View.layout (Getpaddingl EFT (), Mtotalhight, View.getmeasuredwidth (), Mtotalhight + view.getmeasuredheight ()); Mtotalhight + = View.getmeasuredheight ()/2; It takes half the layout}}}
can see in the Onmeasure method inside I did nothing, just called the measurement method, the most important thing is in the onlayout this method inside, you can see it has two branches, a branch is when he animated the branch of the call, a static when the call branch. As you can see, I take half of the height here as a cover, and of course there are people who ask me why I use layout to animate it here? Here I don't answer this question first, then go down. There's a cache function inside, and we'll stick it out first.
/** * Cache View's top and bottom information * * @param i * @param view */void cacheviewtopandbottomifneed (int i, view view) {int viewtop = MVi Ewstopcache.get (I,-1); if (viewtop = =-1) {Mviewstopcache.put (I, View.gettop ());} int viewbttom = Mviewsbottomcache.get (i,-1); if (Viewbttom = =-1) {Mviewsbottomcache.put (I, View.getbottom ());}}
Why do we need to cache this? Because when we call the layout repeatedly, we go to call gettop, and so on, each time we get a change without an aligned point, we need to cache the initial position where we started the move.
The location is well placed so we can come and see how our touch event is handled. Put our code on it.
@Overridepublic boolean dispatchtouchevent (Motionevent event) {if (misexchageanimation)//When there is animation we eat this event to return true; if (event.getaction () = = Motionevent.action_down) {Mtouchindex = Gettouchchildindex (event);//Get click View Indexif ( Mtouchindex! =-1 && mtouchindex! = Getchildcount ()-1)//Click on the last one without opening animation {startanimation ();}} Return Super.dispatchtouchevent (Event)//////////response only to the last card's Click events if (Mtouchindex = = Getchildcount ()-1) {return Super.dispatchtouchevent (event);} else{//Other click events to eat return true;}}
The code here is also very simple is to determine when the click is not in the animation if the animation will return, and then get to click on the child's index call startanimation to open the animation, the subsequent judgment is to judge only the corresponding last card click event.
Here's a look next to the code for the other two functions.
/*** * Gets the child's index * @param event * @return */int Gettouchchildindex (Motionevent event) {for (int i = 0; I &) based on clicked events Lt Getchildcount (); i++) {View view = Getchildat (i); Rect r = new rect (), View.getglobalvisiblerect (r), r = new Rect (R.left, R.top, R.right, R.bottom-view.getheight ()/2); It is important to note that here we are taking the upper half to make a point of judging if (r.contains (int) event.getrawx (), (int) Event.getrawy ())) {return i;}} return-1;}
This function is based on the area of the click to distinguish between the child above, the attention is taken is the upper part to determine.
And then we have the code to start the animation.
/** * Start animation */private void Startanimation () {misexchageanimation = True;mviewsbottomcache.clear (); Mviewstopcache.clear ( ); manimationstarttime = System.currenttimemillis (); View view = Getchildat (Mtouchindex); View.bringtofront (); This sentence code is the main code of the timer = new timer (); Timer.schedule (new TimerTask () {@Overridepublic void run () { Manimationhandler.sendemptymessage (0);}}, 0, 24);}
The method here is also very simple, initialize some variables to empty the cache, and then open a timed task to send a message to handler inside actually this handler what things did not do, just keep on calling Requstlayout let him remove with our OnLayout method, The main code is view.bringtofront () This code is to put the current child on the top level, in fact, is placed in the child array inside the last one, here is why we use OnLayout to do animation. We just need to constantly change the position of onlayout do not need to tube OnDraw inside if the drawing, in fact, the bottom is also drawn. Draw the front child first, and then draw the back.
Summarize this demo:
1: How much the card shows I'm just half of this control
2: Change animation with layout
3: The most important thing is to understand the arrangement of children in BringToFront
4: Cache view top and bottom
Paste all the code, the comments should be very detailed.
/** * * @author Edsheng * @filename Exchagecarldview.java * @date 2015/3/12 * @version v1.0 */public class Exchagecarldvi EW extends viewgroup{private int mtotalhight = 0;//Total Height private Boolean misexchageanimation = false;//whether the Swap animation private Sp Arseintarray Mviewstopcache = new Sparseintarray (); Card Top Border cacheprivate sparseintarray Mviewsbottomcache = new Sparseintarray ()///card bottom Border cacheprivate long Manimationstarttime = 0; Animation start time private long default_animtion_time = 250;//animation time private timer timer; Animation timer private int mtouchindex = -1;//Touchindexhandler Manimationhandler = new Handler () {public void DispatchMessage (an Droid.os.Message msg) {requestlayout ();//update interface layout animation};}; Public Exchagecarldview (Context context) {super (context);} /** * Cache View's top and bottom information * * @param i * @param view */void cacheviewtopandbottomifneed (int i, view view) {int viewtop = Mviews Topcache.get (I,-1); if (viewtop = =-1) {Mviewstopcache.put (I, View.gettop ());} int viewbttom = Mviewsbottomcache.get (i,-1); if (Viewbttom = = -1) {Mviewsbottomcache.put (I, View.getbottom ());}} @Overrideprotected void onmeasure (int widthmeasurespec, int heightmeasurespec) {Measurechildren (Widthmeasurespec, HEIGHTMEASURESPEC); Super.onmeasure (Widthmeasurespec, heightmeasurespec);} /** * Detects and stops the animation */private void Checkanimation () {//When the time is up to stop the animation if (Math.Abs (System.currenttimemillis ()- manimationstarttime)) >= default_animtion_time) {manimationhandler.removemessages (0); Timer.cancel (); Misexchageanimation = false;//postdelayed (new Runnable ()//{////@Override//public void run ()//{//Requestlayout ();/} }, 50);}} @Overrideprotected void OnLayout (Boolean changed, int l, int t, int r, int b) {int count = Getchildcount (); if (Misexchagean Imation)///animation path {for (int i = 0; i < count; i++) {if (Mtouchindex > I)//when the head above the click does not need to change layoutcontinue; View view = Getchildat (i);//cache The first view information is the beginning of the animation information cacheviewtopandbottomifneed (i, view); if (count-1 = = i)//topmost layout {// Calculate how much it should go. total height int total_dis = view.getheight ()/2 * (Count-1-Mtouchindex);//calculates the current linear distance int now_dis = (int) (Total_dis * (System.currenttimemillis ()-manimationstarttime)/Default_animtion_ti me);//return cannot exceed Total_dis this value int dis = math.min (Now_dis, Total_dis); View.layout (View.getleft (), Mviewstopcache.get (i) + Dis, view.getright (), Mviewsbottomcache.get (i) + dis);} else{//remove the top of that layout//each card should be moved view.height 1/2int Total_dis = View.getheight ()/2;//calculates the current linear distance int now_dis = (int) (total _dis * (System.currenttimemillis ()-manimationstarttime)/default_animtion_time);//regression cannot exceed Total_dis this value int dis = Math.min (Now_dis, Total_dis);//placement of Layout view.layout (View.getleft (), Mviewstopcache.get (i)-Dis, view.getright (), Mviewsbottomcache.get (i)-dis);} Detects if the animation is over Checkanimation ();}} else{//Initialize our card when initializing mtotalhight = 0;for (int i = 0; i < count; i++) {View view = Getchildat (i); View.layout (Getpaddingl EFT (), Mtotalhight, View.getmeasuredwidth (), Mtotalhight + view.getmeasuredheight ()); Mtotalhight + = View.getmeasuredheight ()/2; Here's half the layout}}}/** * start animation */private void StArtanimation () {misexchageanimation = True;mviewsbottomcache.clear (); Mviewstopcache.clear (); mAnimationStartTime = System.currenttimemillis (); View view = Getchildat (Mtouchindex); View.bringtofront (); This sentence code is the main code of the timer = new timer (); Timer.schedule (new TimerTask () {@Overridepublic void run () { Manimationhandler.sendemptymessage (0);}}, 0, 24);} @Overridepublic boolean dispatchtouchevent (Motionevent event) {if (misexchageanimation)//When there is animation we eat this event to return true; if (event.getaction () = = Motionevent.action_down) {Mtouchindex = Gettouchchildindex (event);//Get click View Indexif ( Mtouchindex! =-1 && mtouchindex! = Getchildcount ()-1)//Click on the last one without opening animation {startanimation ();}} Return Super.dispatchtouchevent (Event)//////////response only to the last card's Click events if (Mtouchindex = = Getchildcount ()-1) {return Super.dispatchtouchevent (event);} else{//Other click events to eat return true;}} /*** * Get child's Index * @param event * @return */int Gettouchchildindex (Motionevent event) {for (int i = 0; i <) based on clicked events Getchildcount (); i++) {View view = Getchildat (i); Rect r = new rect (), View.getglobalvisiblerect (r), r = new Rect (R.left, R.top, R.right, R.bottom-view.getheight ()/2); It is important to note that here we are taking the upper half to make a point of judging if (r.contains (int) event.getrawx (), (int) Event.getrawy ())) {return i;}} return-1;}}
Finally, the test code.
public class Cardexchagedemo extends activity{@Overrideprotected void OnCreate (Bundle savedinstancestate) {//TODO Auto-generated method Stubsuper.oncreate (savedinstancestate); Requestwindowfeature (Window.feature_no_title); Exchagecarldview Exchageview = new Exchagecarldview (this); View view = new view (this); View.setlayoutparams (New Layoutparams (Layoutparams.match_parent, 300)); View.setbackgroundcolor (Color.yellow); Exchageview.addview (view); View View1 = new View (this); View1.setlayoutparams (New Layoutparams (Layoutparams.match_parent, 300)); View1.setbackgroundcolor (Color.Blue); Exchageview.addview (View1); Button View2 = New button (this); View2.setonclicklistener (new Onclicklistener () {@Overridepublic void OnClick (View v) { Toast.maketext (cardexchagedemo.this, "Hello", 0). Show ();}); View2.setlayoutparams (New Layoutparams (layoutparams.match_parent)); View2.setbackgroundcolor (Color.RED); View2.settext ("Hello"); Exchageview.addview (VIEW2); Exchageview.setbackgroundcolor (Color.green); Setcontentview ( ExChageview);}}
Here to open the download Portal: click here
Android Custom Controls Series tutorial-----Fake new Youku review episode card slide controls