IOS相比于Android,动画效果是一方面优势,IOS相机切换时滑动的动画很不错,看着是有一个3D的效果,而且变化感觉很自然。Android也可以通过Graphics下面的Camera可以实现3D效果,开始尝试着用这个做了一下,效果不理想,滑动之后各组文字之间的距离就变了,从立体空间来说这是合逻辑的,但是看着很别捏。IOS相机的滑动效果文字之间的间隔在滑动的时候是不变的。
后面通过调整TextView X方向的scale使文字看着紧凑一点,然后通过计算的距离的方式,在滑动的时候保持各组文字之间的间隔一致,最后实现的效果还是和IOS的有一定的差距。先上个效果图的。
下面逐步来说下怎么实现:
MainaActivity.java:
往自定义的控件加了6个TextView,对应各个模式。
这里面还实现了一个手势监听,来识别滑动事件。对动画做了一些限制,角度小于30度,滑动距离大于15才能生效。
1 package com.example.androidcustomnview; 2 3 import android.app.Activity; 4 import android.graphics.Color; 5 import android.os.Bundle; 6 import android.util.Log; 7 import android.view.GestureDetector; 8 import android.view.GestureDetector.OnGestureListener; 9 import android.view.View; 10 import android.view.View.OnTouchListener; 11 import android.view.MotionEvent; 12 import android.view.TextureView; 13 import android.view.ViewGroup; 14 import android.view.animation.Animation; 15 import android.view.animation.Animation.AnimationListener; 16 import android.view.animation.AnimationSet; 17 import android.view.animation.TranslateAnimation; 18 import android.widget.FrameLayout; 19 import android.widget.LinearLayout; 20 import android.widget.RelativeLayout; 21 import android.widget.TextView; 22 23 public class MainActivity extends Activity implements OnTouchListener{ 24 25 private static final String TAG = "MainActivity.TAG"; 26 CustomViewL mCustomViewL; 27 String[] name = new String[] {"延时摄影","慢动作","视频","拍照","正方形","全景"}; 28 29 GestureDetector mGestureDetector; 30 RelativeLayout rootView; 31 @Override 32 protected void onCreate(Bundle savedInstanceState) { 33 super.onCreate(savedInstanceState); 34 setContentView(R.layout.activity_main); 35 mCustomViewL = (CustomViewL) findViewById(R.id.mCustomView); 36 rootView = (RelativeLayout) findViewById(R.id.ViewRoot); 37 rootView.setOnTouchListener(this); 38 mCustomViewL.getParent(); 39 mCustomViewL.addIndicator(name); 40 mGestureDetector = new GestureDetector(this, new myGestureDetectorLis()); 48 } 49 50 class myGestureDetectorLis implements GestureDetector.OnGestureListener { 51 52 private static final int degreeLimit = 30; 53 private static final int distanceLimit = 15; 54 55 private boolean isScroll = false; 56 @Override 57 public boolean onDown(MotionEvent e) { 58 // TODO Auto-generated method stub 59 Log.d(TAG, "myGestureDetectorLis onDown"); 60 isScroll = false; 61 return true; 62 } 63 @Override 64 public void onShowPress(MotionEvent e) { 65 // TODO Auto-generated method stub 66 67 } 68 @Override 69 public boolean onSingleTapUp(MotionEvent e) { 70 // TODO Auto-generated method stub 71 return false; 72 } 73 74 @Override 75 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, 76 float distanceY) { 77 // TODO Auto-generated method stub 78 if (isScroll) return false; 79 double degree = Math.atan(Math.abs(e2.getY() - e1.getY()) / Math.abs(e2.getX() - e1.getX())) * 180 /Math.PI; 80 float delta = e2.getX() - e1.getX(); 81 if (delta > distanceLimit && degree < degreeLimit) { 82 Log.d(TAG, "向右滑"); 83 isScroll = true; 84 mCustomViewL.scrollRight(); 85 } else if (delta < -distanceLimit && degree < degreeLimit) { 86 Log.d(TAG, "向左滑"); 87 isScroll = true; 88 mCustomViewL.scrollLeft(); 89 } 90 return false; 91 } 92 93 @Override 94 public void onLongPress(MotionEvent e) { 95 // TODO Auto-generated method stub 96 97 } 98 @Override 99 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 100 float velocityY) { 101 // TODO Auto-generated method stub 102 return false; 103 } 104 105 } 106 107 @Override 108 public boolean onTouch(View v, MotionEvent event) { 109 // TODO Auto-generated method stub 110 return mGestureDetector.onTouchEvent(event); 111 } 112 113 114 }
CustomViewL.java:
自定义的控件,继承自LinearLayout。在onLayout里面,重新计算了下各个子控件的位置,因为各组文字的scale是不一样的,必须重新Layout一下各个子控件的位置,是文字的显示区域和点击区域是一样的,这样给各个子控件设置的onClick事件才有效。
dispatchDraw方法是重绘各个子控件,更具各个子控件到中心控件的位置的距离,设置了各个TextView X方向的scale,为了就是看着要有一个立体的效果。
滑动之后,开始一个动画,动画结束之后重新requestLayout一下,重新计算下各个控件的位置。这个可以连续滑动的,如果这次动画在执行,会保存一下,等动画完了之后会接着跑下一个动画。各个子控件滑动距离的计算有兴趣的可以自己研究下,这里就不赘述了,其实也是数学知识。
1 package com.example.androidcustomnview; 2 3 import android.content.Context; 4 import android.graphics.Camera; 5 import android.graphics.Canvas; 6 import android.graphics.Color; 7 import android.graphics.LinearGradient; 8 import android.graphics.Matrix; 9 import android.graphics.Paint; 10 import android.graphics.Shader; 11 import android.os.Handler; 12 import android.os.Message; 13 import android.util.AttributeSet; 14 import android.util.Log; 15 import android.view.MotionEvent; 16 import android.view.View; 17 import android.view.WindowManager; 18 import android.view.animation.Animation; 19 import android.view.animation.Animation.AnimationListener; 20 import android.view.animation.TranslateAnimation; 21 import android.widget.LinearLayout; 22 import android.widget.TextView; 23 24 public class CustomViewL extends LinearLayout { 25 26 private static final String TAG = "CustomViewL.TAG"; 27 private Matrix mMatrix; 28 Camera mCamera; 29 private int mCurrentItem = 2; 30 private int screenWidth; 31 private Paint mPaint; 32 33 public static final float ItemScale = 0.1f; 34 35 public CustomViewL(Context context) { 36 super(context); 37 // TODO Auto-generated constructor stub 38 initView(context); 39 } 40 41 public CustomViewL(Context context, AttributeSet attrs, int defStyleAttr, 42 int defStyleRes) { 43 super(context, attrs, defStyleAttr, defStyleRes); 44 initView(context); 45 } 46 47 public CustomViewL(Context context, AttributeSet attrs, int defStyleAttr) { 48 super(context, attrs, defStyleAttr); 49 initView(context); 50 } 51 52 public CustomViewL(Context context, AttributeSet attrs) { 53 super(context, attrs); 54 initView(context); 55 } 56 57 private void initView(Context context) { 60 screenWidth = ((WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE)) 61 .getDefaultDisplay().getWidth(); 63 } 64 65 @Override 66 protected void onLayout(boolean changed, int l, int t, int r, int b) { 67 Log.d(TAG, "onLayout "); 68 super.onLayout(changed, l , t, r, b); 69 View v = getChildAt(mCurrentItem); 70 int delta = getWidth() / 2 - v.getLeft() - v.getWidth()/2; 71 72 for (int i = 0; i < getChildCount(); i++) { 73 View v1 = getChildAt(i); 74 if (i == mCurrentItem) { 75 v1.layout(v1.getLeft() + delta, v1.getTop(), 76 v1.getRight() + delta, v1.getBottom()); 77 continue; 78 } 79 float mScale = Math.abs(i - mCurrentItem) * ItemScale; 80 int move = (int)(v1.getWidth() * mScale / 2); 81 if (i < mCurrentItem) { 82 for (int j = i + 1; j < mCurrentItem; j++) { 83 View v2 = getChildAt(j); 84 move += (int) (v2.getWidth() * Math.abs(j - mCurrentItem) * ItemScale); 85 } 86 } else { 87 for (int j = i - 1; j > mCurrentItem; j--) { 88 View v2 = getChildAt(j); 89 move += (int)(v2.getWidth() * Math.abs(j - mCurrentItem) * ItemScale); 90 } 91 move = -move; 92 } 93 v1.layout(v1.getLeft() + delta + move, v1.getTop(), 94 v1.getRight() + delta + move, v1.getBottom()); 95 } 96 mRequstLayout = false; 97 } 98 99 @Override 100 protected void dispatchDraw(Canvas canvas) { 101 int count = getChildCount(); 102 for (int i = 0; i < count; i++) { 103 updateChildItem(canvas,i); 104 } 105 } 106 107 public void updateChildItem(Canvas canvas,int item) { 108 // Log.d(TAG, "updateChildItem"); 110 View v = getChildAt(item); 111 float desi = 1- Math.abs(item - mCurrentItem) * ItemScale; 112 ((TextView)v).setScaleX(desi); 113 drawChild(canvas, v, getDrawingTime()); 114 updateTextColor(); 115 } 118 private void updateTextColor() { 119 for (int i =0 ; i < getChildCount(); i++) { 120 if (i == mCurrentItem) { 121 ((TextView)getChildAt(i)).setTextColor(Color.YELLOW); 122 } else { 123 ((TextView)getChildAt(i)).setTextColor(Color.WHITE); 124 } 125 } 126 } 128 boolean scroolToRight = false; 129 130 public void scrollRight() { 131 if (mRequstLayout) return; 132 if (mCurrentItem > 0) { 133 if (mAnimationRunning) { 134 if (AnimationRunningCount < 1) { 135 currentItemCopy = mCurrentItem - 1; 136 AnimationRunningCount++; 137 scroolToRight = true; 138 } 139 return; 140 } 141 mCurrentItem--; 142 startTraAnimation(mCurrentItem,mCurrentItem + 1); 143 updateTextColor(); 144 } 145 } 146 147 private int currentItemCopy; 148 public void scrollLeft() { 149 if (mRequstLayout) return; 150 if (mCurrentItem < getChildCount() - 1) { 151 if (mAnimationRunning) { 152 if (AnimationRunningCount < 1) { 153 currentItemCopy = mCurrentItem + 1; 154 AnimationRunningCount++; 155 scroolToRight = false; 156 } 157 return; 158 } 159 mCurrentItem++; 160 startTraAnimation(mCurrentItem,mCurrentItem-1); 161 updateTextColor(); 162 } 163 } 164 165 public void addIndicator(String[] name) { 166 for (int i=0; i< name.length; i++) { 167 TextView mTextView = new TextView(getContext()); 168 mTextView.setText(name[i]); 169 mTextView.setTextColor(Color.WHITE); 170 mTextView.setLines(1); 171 LinearLayout.LayoutParams ll = new LinearLayout.LayoutParams( 172 LinearLayout.LayoutParams.WRAP_CONTENT, 173 LinearLayout.LayoutParams.WRAP_CONTENT); 174 ll.setMargins(20, 0, 20, 0); 175 addView(mTextView,ll); 176 } 177 } 178 179 class myAnimationListener implements android.view.animation.Animation.AnimationListener { 180 181 @Override 182 public void onAnimationStart(Animation animation) { 183 Log.d(TAG, "onAnimationStart "); 184 mAnimationRunning = true; 185 } 186 @Override 187 public void onAnimationEnd(Animation animation) { 188 // TODO Auto-generated method stub 189 Log.d(TAG, "onAnimationEnd "); 190 191 for (int i= 0; i < getChildCount(); i++) { 192 getChildAt(i).clearAnimation(); 193 } 194 mRequstLayout = true; 195 requestLayout(); 196 mAnimationRunning = false; 197 if (AnimationRunningCount > 0) { 198 CustomViewL.this.post(new Runnable() { 199 @Override 200 public void run() { 201 // TODO Auto-generated method stub 202 AnimationRunningCount--; 203 mCurrentItem = currentItemCopy; 204 int lastItem = scroolToRight ? currentItemCopy + 1 : currentItemCopy - 1; 205 startTraAnimation(currentItemCopy,lastItem); 206 updateTextColor(); 207 } 208 }); 209 } 210 } 211 @Override 212 public void onAnimationRepeat(Animation animation) { 213 } 214 215 } 216 217 private int AnimitionDurationTime = 300; 218 private int AnimationRunningCount = 0; 219 private boolean mAnimationRunning = false; 220 private boolean mRequstLayout = false; 221 public void startTraAnimation(int item,int last) { 222 Log.d(TAG, "startTraAnimation item = " + item); 223 View v = getChildAt(item); 224 final int width = v.getWidth(); 225 final int childCount = getChildCount(); 226 int traslate = getWidth()/2 - v.getLeft() - width/2; 227 228 int currentItemWidthScale = (int) (width * ItemScale); 229 230 for (int i = 0; i < childCount; i++) { 231 int delta = currentItemWidthScale / 2; 233 Log.d(TAG, " i = " + i + " delta before = " + delta); 234 if (i < item) { 235 delta = -delta; 236 for (int j = i; j < item; j++) { 237 int a; 238 if (i == j) { 239 a = (int)(getChildAt(j).getWidth() * ItemScale / 2); 240 } else { 241 a = (int)(getChildAt(j).getWidth() * ItemScale); 242 } 243 delta = item < last ? delta - a : delta + a; 244 } 245 } else if (i > item){ 246 for (int j = item + 1; j <= i; j++) { 247 int a; 248 if (j == i) { 249 a = (int)(getChildAt(j).getWidth() * ItemScale / 2); 250 } else { 251 a = (int)(getChildAt(j).getWidth() * ItemScale); 252 } 253 delta = item < last ? delta - a : delta + a; 254 } 255 } else { 256 delta = 0; 257 } 258 Log.d(TAG, "delta = " + delta); 259 delta += traslate; 260 TranslateAnimation translateAni = new TranslateAnimation(0, delta, 0, 0); 261 translateAni.setDuration(AnimitionDurationTime); 262 translateAni.setFillAfter(true); 263 if (i == item) translateAni.setAnimationListener(new myAnimationListener()); 264 mAnimationRunning = true; 265 getChildAt(i).startAnimation(translateAni); 266 } 268 } 269 }
最后说一下布局文件,两边本来是要做一个阴影效果的,为了简便,复习了下PS,就在上面盖了张图片,显得两边有阴影。
<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>
整个来说其实也不复杂,有好些数学计算,几何问题,效果也没达到iphone的效果,如果有大神有想法,可以指导下。