Home>

Compared with android, the animation effect is one of the advantages.The sliding animation when the ios camera switches is great,Looking at it has a 3d effect, and the change feels natural.Android can also achieve 3D effects through the camera under graphics. Start to try to do this with a bit,The effect is not ideal.After sliding, the distance between each group of text changes,This is logical from a stereo perspective,But look at it very hard.The gap between the text of the ios camera's sliding effect is constant when sliding.

Later, by adjusting the scale of the textview x direction to make the text look more compact,Then by calculating the distance,Keep the space between the groups of text consistent when sliding,The final effect is still a certain distance from ios.First on the effect map.

Here's how to achieve it step by step:

mainaactivity.java:

Added 6 textviews to the custom control, corresponding to each mode.

It also implements a gesture listener,To identify swipe events.Some restrictions on animation,The angle is less than 30 degrees, and the sliding distance is greater than 15 to take effect.

package com.example.androidcustomnview;
import android.app.activity;
import android.graphics.color;
import android.os.bundle;
import android.util.log;
import android.view.gesturedetector;
import android.view.gesturedetector.ongesturelistener;
import android.view.view;
import android.view.view.ontouchlistener;
import android.view.motionevent;
import android.view.textureview;
import android.view.viewgroup;
import android.view.animation.animation;
import android.view.animation.animation.animationlistener;
import android.view.animation.animationset;
import android.view.animation.translateanimation;
import android.widget.framelayout;
import android.widget.linearlayout;
import android.widget.relativelayout;
import android.widget.textview;
public class mainactivity extends activity implements ontouchlistener {
 private static final string tag="mainactivity.tag";
 customviewl mcustomviewl;
 string [] name=new string [] {"Time-lapse photography", "Slow motion", "Video", "Photographing", "Square", "Panorama"};
 gesturedetector mgesturedetector;
 relativelayout rootview;
 @override
 protected void oncreate (bundle savedinstancestate) {
  super.oncreate (savedinstancestate);
  setcontentview (r.layout.activity_main);
  mcustomviewl=(customviewl) findviewbyid (r.id.mcustomview);
  rootview=(relativelayout) findviewbyid (r.id.viewroot);
  rootview.setontouchlistener (this);
  mcustomviewl.getparent ();
  mcustomviewl.addindicator (name);
  mgesturedetector=new gesturedetector (this, new mygesturedetectorlis ());48}
 class mygesturedetectorlis implements gesturedetector.ongesturelistener {
  private static final int degreelimit=30;
  private static final int distancelimit=15;
  private boolean isscroll=false;
  @override
  public boolean ondown (motionevent e) {
   //todo auto-generated method stub
   log.d (tag, "mygesturedetectorlis ondown");
   isscroll=false;
   return true;
  }
  @override
  public void onshowpress (motionevent e) {
   //todo auto-generated method stub
  }
  @override
  public boolean onsingletapup (motionevent e) {
   //todo auto-generated method stub
   return false;
  }
  @override
  public boolean onscroll (motionevent e1, motionevent e2, float distancex,    float distancey) {
   //todo auto-generated method stub
   if (isscroll) return false;
   double degree=math.atan (math.abs (e2.gety ()-e1.gety ())/math.abs (e2.getx ()-e1.getx ())) * 180 /math.pi;
   float delta=e2.getx ()-e1.getx ();
   if (delta>distancelimit&°ree<degreelimit) {
    log.d (tag, "Slide right");
    isscroll=true;
    mcustomviewl.scrollright ();
   } else if (delta<-distancelimit&°ree<degreelimit) {
    log.d (tag, "Slide left");
    isscroll=true;
    mcustomviewl.scrollleft ();
   }
   return false;
  }
  @override
  public void onlongpress (motionevent e) {
   //todo auto-generated method stub
  }
  @override
  public boolean onfling (motionevent e1, motionevent e2, float velocityx,    float velocityy) {
   //todo auto-generated method stub
   return false;
  }
 }
 @override
 public boolean ontouch (view v, motionevent event) {
  //todo auto-generated method stub
  return mgesturedetector.ontouchevent (event);
 }
}

customviewl.java:

Custom controls,Inherited from linearlayout. In onlayout, the position of each child control is recalculated.Because the scale of each group of text is different,You must re-lay out the position of each child control,The text display area is the same as the click area.In this way, the onclick event set for each child control is valid.

The dispatchdraw method is to redraw each child control,The distance from each child control to the position of the center control,Set the x-direction scale of each textview in order to see a three-dimensional effect.

After swiping,Start an animation,After the animation ends, re-requestlayout and recalculate the position of each control.This one can slide continuously,If the animation is running this time,Will save it,After the animation is finished, it will continue to the next animation.The calculation of the sliding distance of each sub-control can be studied by yourself.I won't go into details here.In fact, it is mathematical knowledge.

package com.example.androidcustomnview;
import android.content.context;
import android.graphics.camera;
import android.graphics.canvas;
import android.graphics.color;
import android.graphics.lineargradient;
import android.graphics.matrix;
import android.graphics.paint;
import android.graphics.shader;
import android.os.handler;
import android.os.message;
import android.util.attributeset;
import android.util.log;
import android.view.motionevent;
import android.view.view;
import android.view.windowmanager;
import android.view.animation.animation;
import android.view.animation.animation.animationlistener;
import android.view.animation.translateanimation;
import android.widget.linearlayout;
import android.widget.textview;
public class customviewl extends linearlayout {
 private static final string tag="customviewl.tag";
 private matrix mmatrix;
 camera mcamera;
 private int mcurrentitem=2;
 private int screenwidth;
 private paint mpaint;
 public static final float itemscale=0.1f;
 public customviewl (context context) {
  super (context);
  //todo auto-generated constructor stub
  initview (context);
 }
 public customviewl (context context, attributeset attrs, int defstyleattr,   int defstyleres) {
  super (context, attrs, defstyleattr, defstyleres);
  initview (context);
 }
 public customviewl (context context, attributeset attrs, int defstyleattr) {
  super (context, attrs, defstyleattr);
  initview (context);
 }
 public customviewl (context context, attributeset attrs) {
  super (context, attrs);
  initview (context);
 }
 private void initview (context context) {
  screenwidth=((windowmanager) getcontext (). getsystemservice (context.window_service))
    .getdefaultdisplay (). getwidth ();
 }
 @override
 protected void onlayout (boolean changed, int l, int t, int r, int b) {
  log.d (tag, "onlayout");
  super.onlayout (changed, l, t, r, b);
  view v=getchildat (mcurrentitem);
  int delta=getwidth ()/2-v.getleft ()-v.getwidth ()/2;
  for (int i=0;i<getchildcount ();i ++) {
   view v1=getchildat (i);
   if (i == mcurrentitem) {
    v1.layout (v1.getleft () + delta, v1.gettop (),     v1.getright () + delta, v1.getbottom ());
    continue;
   }
   float mscale=math.abs (i-mcurrentitem) * itemscale;
   int move=(int) (v1.getwidth () * mscale/2);
   if (i<mcurrentitem) {
    for (int j=i + 1;j<mcurrentitem;j ++) {
     view v2=getchildat (j);
     move +=(int) (v2.getwidth () * math.abs (j-mcurrentitem) * itemscale);
    }
   } else {
    for (int j=i-1;j>mcurrentitem;j--) {
     view v2=getchildat (j);
     move +=(int) (v2.getwidth () * math.abs (j-mcurrentitem) * itemscale);
    }
    move=-move;
   }
   v1.layout (v1.getleft () + delta + move, v1.gettop (),     v1.getright () + delta + move, v1.getbottom ());
  }
  mrequstlayout=false;
 }
 @override
 protected void dispatchdraw (canvas canvas) {
  int count=getchildcount ();
  for (int i=0;i<count;i ++) {
   updatechilditem (canvas, i);
  }
 }
 public void updatechilditem (canvas canvas, int item) {
//log.d (tag, "updatechilditem");
  view v=getchildat (item);
  float desi=1- math.abs (item-mcurrentitem) * itemscale;
  ((textview) v) .setscalex (desi);
  drawchild (canvas, v, getdrawingtime ());
  updatetextcolor ();
 }
 private void updatetextcolor () {
  for (int i=0;i<getchildcount ();i ++) {
   if (i == mcurrentitem) {
    ((textview) getchildat (i)). settextcolor (color.yellow);
   } else {
    ((textview) getchildat (i)). settextcolor (color.white);
   }
  }
 }
 boolean scrooltoright=false;
 public void scrollright () {
  if (mrequstlayout) return;
  if (mcurrentitem>0) {
   if (manimationrunning) {
    if (animationrunningcount<1) {
     currentitemcopy=mcurrentitem-1;
     animationrunningcount ++;
     scrooltoright=true;
    }
    return;
   }
   mcurrentitem--;
   starttraanimation (mcurrentitem, mcurrentitem + 1);
   updatetextcolor ();
  }
 }
 private int currentitemcopy;
 public void scrollleft () {
  if (mrequstlayout) return;
  if (mcurrentitem<getchildcount ()-1) {
   if (manimationrunning) {
    if (animationrunningcount<1) {
     currentitemcopy=mcurrentitem + 1;
     animationrunningcount ++;
     scrooltoright=false;
    }
    return;
   }
   mcurrentitem ++;
   starttraanimation (mcurrentitem, mcurrentitem-1);
   updatetextcolor ();
  }
 }
 public void addindicator (string [] name) {
  for (int i=0;i<name.length;i ++) {
   textview mtextview=new textview (getcontext ());
   mtextview.settext (name [i]);
   mtextview.settextcolor (color.white);
   mtextview.setlines (1);
   linearlayout.layoutparams ll=new linearlayout.layoutparams (
     linearlayout.layoutparams.wrap_content,     linearlayout.layoutparams.wrap_content);
   ll.setmargins (20, 0, 20, 0);
   addview (mtextview, ll);
  }
 }
 class myanimationlistener implements android.view.animation.animation.animationlistener {
  @override
  public void onanimationstart (animation animation) {
   log.d (tag, "onanimationstart");
   manimationrunning=true;
  }
  @override
  public void onanimationend (animation animation) {
   //todo auto-generated method stub
   log.d (tag, "onanimationend");
   for (int i=0;i<getchildcount ();i ++) {
    getchildat (i) .clearanimation ();
   }
   mrequstlayout=true;
   requestlayout ();
   manimationrunning=false;
   if (animationrunningcount>0) {
    customviewl.this.post (new runnable () {
     @override
     public void run () {
      //todo auto-generated method stub
      animationrunningcount--;
      mcurrentitem=currentitemcopy;
      int lastitem=scrooltoright?currentitemcopy + 1:currentitemcopy-1;
      starttraanimation (currentitemcopy, lastitem);
      updatetextcolor ();
     }
    });
   }
  }
  @override
  public void onanimationrepeat (animation animation) {
  }
 }
 private int animitiondurationtime=300;
 private int animationrunningcount=0;
 private boolean manimationrunning=false;
 private boolean mrequstlayout=false;
 public void starttraanimation (int item, int last) {
  log.d (tag, "starttraanimation item =" + item);
  view v=getchildat (item);
  final int width=v.getwidth ();
  final int childcount=getchildcount ();
  int traslate=getwidth ()/2-v.getleft ()-width/2;
  int currentitemwidthscale=(int) (width * itemscale);
  for (int i=0;i<childcount;i ++) {
   int delta=currentitemwidthscale/2;
   log.d (tag, "i =" + i + "delta before =" + delta);
   if (i<item) {
    delta=-delta;
    for (int j=i;j<item;j ++) {
     int a;
     if (i == j) {
      a=(int) (getchildat (j) .getwidth () * itemscale/2);
     } else {
      a=(int) (getchildat (j) .getwidth () * itemscale);
     }
     delta=item<last?delta-a:delta + a;
    }
   } else if (i&item;item) {
    for (int j=item + 1;j<= i;j ++) {
     int a;
     if (j == i) {
      a=(int) (getchildat (j) .getwidth () * itemscale/2);
     } else {
      a=(int) (getchildat (j) .getwidth () * itemscale);
     }
     delta=item<last?delta-a:delta + a;
    }
   } else {
    delta=0;
   }
   log.d (tag, "delta =" + delta);
   delta +=traslate;
   translateanimation translateani=new translateanimation (0, delta, 0, 0);
   translateani.setduration (animitiondurationtime);
   translateani.setfillafter (true);
   if (i == item) translateani.setanimationlistener (new myanimationlistener ());
   manimationrunning=true;
   getchildat (i) .startanimation (translateani);
  }
 }
}

Finally, about the layout file,Both sides were meant to be a shadow effect,For simplicity,After reviewing ps, I covered a picture on it,Looks like shadows on both sides.

<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 tools:context="com.example.androidcustomnview.mainactivity">
 <relativelayout
  android:id="@ + id/viewroot"
  android:gravity="center"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <com.example.androidcustomnview.customviewl
   android:orientation="horizontal"
   android:background="@ android:color/background_dark"
   android:id="@ + id/mcustomview"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   >
  </com.example.androidcustomnview.customviewl>
  <imageview
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_alignleft="@ id/mcustomview"
   android:layout_aligntop="@ id/mcustomview"
   android:layout_alignright="@ id/mcustomview"
   android:layout_alignbottom="@ id/mcustomview"
   android:background="@ drawable/test" />
 </relativelayout>
</relativelayout>

In fact, it is not complicated at all.There are many mathematical calculations,Geometry problem,The effect has not reached the effect of the iPhone.Can be guided.

  • Previous AngularJS basic ng-init directive simple example
  • Next PHP related operations on 2D arrays (sorting, conversion, blanking, etc)