Android实现IOS相机滑动控件
ios相比于android,动画效果是一方面优势,ios相机切换时滑动的动画很不错,看着是有一个3d的效果,而且变化感觉很自然。android也可以通过graphics下面的camera可以实现3d效果,开始尝试着用这个做了一下,效果不理想,滑动之后各组文字之间的距离就变了,从立体空间来说这是合逻辑的,但是看着很别捏。ios相机的滑动效果文字之间的间隔在滑动的时候是不变的。
后面通过调整textview x方向的scale使文字看着紧凑一点,然后通过计算的距离的方式,在滑动的时候保持各组文字之间的间隔一致,最后实现的效果还是和ios的有一定的差距。先上个效果图的。
下面逐步来说下怎么实现:
mainaactivity.java:
往自定义的控件加了6个textview,对应各个模式。
这里面还实现了一个手势监听,来识别滑动事件。对动画做了一些限制,角度小于30度,滑动距离大于15才能生效。
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[] {"延时摄影","慢动作","视频","拍照","正方形","全景"}; 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 && degree < degreelimit) { log.d(tag, "向右滑"); isscroll = true; mcustomviewl.scrollright(); } else if (delta < -distancelimit && degree < degreelimit) { log.d(tag, "向左滑"); 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:
自定义的控件,继承自linearlayout。在onlayout里面,重新计算了下各个子控件的位置,因为各组文字的scale是不一样的,必须重新layout一下各个子控件的位置,是文字的显示区域和点击区域是一样的,这样给各个子控件设置的onclick事件才有效。
dispatchdraw方法是重绘各个子控件,更具各个子控件到中心控件的位置的距离,设置了各个textview x方向的scale,为了就是看着要有一个立体的效果。
滑动之后,开始一个动画,动画结束之后重新requestlayout一下,重新计算下各个控件的位置。这个可以连续滑动的,如果这次动画在执行,会保存一下,等动画完了之后会接着跑下一个动画。各个子控件滑动距离的计算有兴趣的可以自己研究下,这里就不赘述了,其实也是数学知识。
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){ 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); } } }
最后说一下布局文件,两边本来是要做一个阴影效果的,为了简便,复习了下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的效果,如果有大神有想法,可以指导下。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。