Android星球效果实现
在项目中看着这个旋转效果挺炫的,就抽取出来做个记录。主要是使用carrousellayout 稍微修改
carrousellayout代码demo下载z地址:github
https://github.com/lyfkai/androidcarrousellayout
主要代码如下 :
public class carrousellayout extends relativelayout {
private context mcontext;
//自动旋转 默认不自动
private boolean mautorotation;
//旋转间隔时间 默认设置为2秒
private int mrotationtime;
//旋转木马旋转半径 圆的半径
private float mcarrouselr;
//camera和旋转木马距离
private float mdistance = 2f * mcarrouselr;
//旋转方向 分0顺时针和 1逆时针 俯视旋转木马看
private int mrotatedirection;
//handler
private carrouselrotatehandler mhandler;
//手势处理
private gesturedetector mgesturedetector;
//x旋转
private int mrotationx;
//z旋转
private int mrotationz;
//旋转的角度
private float mangle = 0;
//旋转木马子view
private list<view> mcarrouselviews = new arraylist<>();
//旋转木马子view的数量
private int viewcount;
//半径扩散动画
private valueanimator manimationr;
//记录最后的角度 用来记录上一次取消touch之后的角度
private float mlastangle;
//是否在触摸
private boolean istouching;
//旋转动画
private valueanimator restanimator;
//选中item
private int selectitem;
//item选中回调接口
private oncarrouselitemselectedlistener moncarrouselitemselectedlistener;
//item点击回调接口
private oncarrouselitemclicklistener moncarrouselitemclicklistener;
//x轴旋转动画
private valueanimator xanimation;
//z轴旋转动画
private valueanimator zanimation;
private boolean isfinish = true;//惯性动画是否结束
private boolean isfling;
public carrousellayout(context context) {
this(context, null);
}
public carrousellayout(context context, attributeset attrs) {
this(context, attrs, 0);
}
public carrousellayout(context context, attributeset attrs, int defstyleattr) {
super(context, attrs, defstyleattr);
init(context, attrs);
}
private void init(context context, attributeset attrs) {
this.mcontext = context;
typedarray typedarray = context.obtainstyledattributes(attrs, r.styleable.carrousellayout);
mautorotation = typedarray.getboolean(r.styleable.carrousellayout_autorotation, false);
mrotationtime = typedarray.getint(r.styleable.carrousellayout_rotationtime, 2000);
mcarrouselr = typedarray.getdimension(r.styleable.carrousellayout_r, 200);
mrotatedirection = typedarray.getint(r.styleable.carrousellayout_rotatedirection, 0);
typedarray.recycle();
mgesturedetector = new gesturedetector(context, getgesturedetectorcontroller());
inithandler();
}
/**
* 初始化handler对象
*/
private void inithandler() {
mhandler = new carrouselrotatehandler(mautorotation, mrotationtime, mrotatedirection) {
@override
public void onrotating(carrouselrotatedirection rotatedirection) {//接受到需要旋转指令
try {
if (viewcount != 0) {//判断自动滑动从那边开始
int perangle = 0;
switch (rotatedirection) {
case clockwise:
perangle = 360 / viewcount;
break;
case anticlockwise:
perangle = -360 / viewcount;
break;
}
if (mangle == 360) {
mangle = 0f;
}
if (isfinish)
startanimrotation(mangle + perangle, null);
}
} catch (exception e) {
e.printstacktrace();
}
}
};
}
private gesturedetector.simpleongesturelistener getgesturedetectorcontroller() {
return new gesturedetector.simpleongesturelistener() {
@override
public boolean onfling(motionevent e1, motionevent e2, float velocityx, float velocityy) {
isfling = true;
if (e2.getx() - e1.getx() < 0) { // 左滑
setautoscrolldirection(carrouselrotatedirection.clockwise);
} else {
setautoscrolldirection(carrouselrotatedirection.anticlockwise);
}
return true;
}
@override
public boolean onscroll(motionevent e1, motionevent e2, float distancex, float distancey) {
isfling = false;
if (distancex > 0) { // 左滑
setautoscrolldirection(carrouselrotatedirection.clockwise);
} else {
setautoscrolldirection(carrouselrotatedirection.anticlockwise);
}
//转换成弧度
double radians = math.toradians(mrotationz);
//math.cos(radians) 返回对应的radians弧度的余弦值
mangle += math.cos(radians) * (distancex / 4) + math.sin(radians) * (distancey / 4);
// log.e("mangle", mangle + "");
log.e("mrotationz", mrotationz + "");
//初始化
refreshlayout();
return true;
}
};
}
/**
* 初始化 计算平均角度后各个子view的位置
*/
public void refreshlayout() {
for (int i = 0; i < mcarrouselviews.size(); i++) {
double radians = mangle + 180 - i * 360 / viewcount;
float x0 = (float) math.sin(math.toradians(radians)) * mcarrouselr;
float y0 = (float) math.cos(math.toradians(radians)) * mcarrouselr;
float scale0 = (mdistance - y0) / (mdistance + mcarrouselr);
mcarrouselviews.get(i).setscalex(scale0 < 0.5f ? 0.5f : scale0);
mcarrouselviews.get(i).setscaley(scale0 < 0.5f ? 0.5f : scale0);
if (mcarrouselviews.get(i) instanceof relativelayout) {
if (((relativelayout) mcarrouselviews.get(i)).getchildcount() >= 2) {
if (((relativelayout) mcarrouselviews.get(i)).getchildat(0) instanceof imageview) {
int value = float.valueof((scale0 < 0.5f ? 0.5f : scale0) * 255).intvalue();
((imageview) ((relativelayout) mcarrouselviews.get(i)).getchildat(0)).setimagealpha(value);
// ((imageview)((relativelayout) mcarrouselviews.get(i)).getchildat(0)).setalpha(scale0);
}
// ((relativelayout) mcarrouselviews.get(i)).getchildat(0).setalpha(scale0);
if (((relativelayout) mcarrouselviews.get(i)).getchildat(1) instanceof textview) {
((relativelayout) mcarrouselviews.get(i)).getchildat(1).setalpha(scale0 < 0.5f ? 0.5f : scale0);
}
}
}
float rotationx_y = (float) math.sin(math.toradians(mrotationx * math.cos(math.toradians(radians)))) * mcarrouselr;
float rotationz_y = -(float) math.sin(math.toradians(-mrotationz)) * x0;
float rotationz_x = (((float) math.cos(math.toradians(-mrotationz)) * x0) - x0);
mcarrouselviews.get(i).settranslationx(x0 + rotationz_x);
mcarrouselviews.get(i).settranslationy(rotationx_y + rotationz_y);
}
list<view> arrayviewlist = new arraylist<>();
arrayviewlist.clear();
for (int i = 0; i < mcarrouselviews.size(); i++) {
arrayviewlist.add(mcarrouselviews.get(i));
}
sortlist(arrayviewlist);
postinvalidate();
}
/**
* 排序
* 對子view 排序,然后根据变化选中是否重绘,这样是为了实现view 在显示的时候来控制当前要显示的是哪三个view,可以改变排序看下效果
*
* @param list
*/
@suppresswarnings("unchecked")
private <t> void sortlist(list<view> list) {
@suppresswarnings("rawtypes")
comparator comparator = new sortcomparator();
t[] array = list.toarray((t[]) new object[list.size()]);
arrays.sort(array, comparator);
int i = 0;
listiterator<t> it = (listiterator<t>) list.listiterator();
while (it.hasnext()) {
it.next();
it.set(array[i++]);
}
for (int j = 0; j < list.size(); j++) {
list.get(j).bringtofront();
}
}
/**
* 筛选器
*/
private class sortcomparator implements comparator<view> {
@override
public int compare(view o1, view o2) {
return (int) (1000 * o1.getscalex() - 1000 * o2.getscalex());
}
}
@override
protected void onsizechanged(int w, int h, int oldw, int oldh) {
super.onsizechanged(w, h, oldw, oldh);
refreshlayout();
if (mautorotation) {
mhandler.sendemptymessage(carrouselrotatehandler.mmsgwhat);
}
}
@override
protected void onlayout(boolean changed, int l, int t, int r, int b) {
super.onlayout(changed, l, t, r, b);
if (changed) {
checkchildview();
startanimationr();
}
}
/**
* 旋转木马半径打开动画
*/
public void startanimationr() {
startanimationr(1f, mcarrouselr);
}
/**
* 旋转木马半径动画
*
* @param isopen 是否打开 否则关闭
*/
public void startanimationr(boolean isopen) {
if (isopen) {
startanimationr(1f, mcarrouselr);
} else {
startanimationr(mcarrouselr, 1f);
}
}
/**
* 半径扩散、收缩动画 根据设置半径来实现
*
* @param from
* @param to
*/
public void startanimationr(float from, float to) {
manimationr = valueanimator.offloat(from, to);
manimationr.addupdatelistener(new valueanimator.animatorupdatelistener() {
@override
public void onanimationupdate(valueanimator valueanimator) {
mcarrouselr = (float) valueanimator.getanimatedvalue();
refreshlayout();
}
});
manimationr.setinterpolator(new decelerateinterpolator());
manimationr.setduration(0);
manimationr.start();
}
public void checkchildview() {
//先清空views里边可能存在的view防止重复
for (int i = 0; i < mcarrouselviews.size(); i++) {
mcarrouselviews.remove(i);
}
final int count = getchildcount(); //获取子view的个数
viewcount = count;
for (int i = 0; i < count; i++) {
final view view = getchildat(i); //获取指定的子view
final int position = i;
mcarrouselviews.add(view);
view.setonclicklistener(new onclicklistener() {
@override
public void onclick(view v) {
if (moncarrouselitemclicklistener != null) {
moncarrouselitemclicklistener.onitemclick(view, position);
}
}
});
}
}
/**
* 复位
*/
private void restview() {
if (viewcount == 0) {
return;
}
float resultangle = 0;
//平均角度
float averageangle = 360 / viewcount;
if (mangle < 0) {
averageangle = -averageangle;
}
float minvalue = (int) (mangle / averageangle) * averageangle;//最小角度
float maxvalue = (int) (mangle / averageangle) * averageangle + averageangle;//最大角度
if (mangle >= 0) {//分为是否小于0的情况
if (mangle - mlastangle > 0) {
resultangle = maxvalue;
} else {
resultangle = minvalue;
}
} else {
if (mangle - mlastangle < 0) {
resultangle = maxvalue;
} else {
resultangle = minvalue;
}
}
startanimrotation(resultangle, null);
}
/**
* 动画旋转
*
* @param resultangle
* @param complete
*/
private void startanimrotation(float resultangle, final runnable complete) {
if (mangle == resultangle) {
return;
}
if (!isfinish) {
restanimator = valueanimator.offloat(mangle, mangle + (resultangle - mangle) * 5);
//设置旋转匀速插值器
restanimator.setinterpolator(new decelerateinterpolator());
restanimator.setduration(1000);
} else {
restanimator = valueanimator.offloat(mangle, resultangle);
//设置旋转匀速插值器
restanimator.setinterpolator(new linearinterpolator());
restanimator.setduration(8000);
}
restanimator.addupdatelistener(new valueanimator.animatorupdatelistener() {
@override
public void onanimationupdate(valueanimator animation) {
if (istouching == false) {
mangle = (float) animation.getanimatedvalue();
refreshlayout();
}
}
});
restanimator.addlistener(new animator.animatorlistener() {
@override
public void onanimationstart(animator animation) {
}
@override
public void onanimationend(animator animation) {
if (istouching == false) {
selectitem = calculateitem();
if (selectitem < 0) {
selectitem = viewcount + selectitem;
}
if (moncarrouselitemselectedlistener != null) {
moncarrouselitemselectedlistener.selected(mcarrouselviews.get(selectitem), selectitem);
}
if (isfinish == false) {
isfinish = true;
}
isfling = false;
}
}
@override
public void onanimationcancel(animator animation) {
}
@override
public void onanimationrepeat(animator animation) {
}
});
restanimator.start();
}
/**
* 通过角度计算是第几个item
*
* @return
*/
private int calculateitem() {
return (int) (mangle / (360 / viewcount)) % viewcount;
}
/**
* 触摸停止计时器
*/
@override
public boolean dispatchtouchevent(motionevent ev) {
return setcanautorotation(ev);
}
/**
* 触摸方法
*
* @param event
* @return
*/
@override
public boolean ontouchevent(motionevent event) {
return true;
}
/**
* 触摸时停止自动加载
*
* @param event
*/
float downx = 0;
float downy = 0;
long currentms = 0;
float movex;
float movey;
long movetime;
public boolean setcanautorotation(motionevent event) {
boolean result = mgesturedetector.ontouchevent(event);
if (result) {
this.getparent().requestdisallowintercepttouchevent(true);//通知父控件勿拦截本控件
}
if (event.getaction() == motionevent.action_move) {
if (!result) {
this.getparent().requestdisallowintercepttouchevent(true);//通知父控件勿拦截本控件
}
}
switch (event.getaction()) {
case motionevent.action_down:
mlastangle = mangle;
downx = event.getx();//float downx
downy = event.gety();//float downy
currentms = system.currenttimemillis();//long currentms 获取系统时间
break;
case motionevent.action_move:
movex = event.getx() - downx;//x轴距离
movey = event.gety() - downy;//y轴距离
movetime = system.currenttimemillis() - currentms;//移动时间
if (math.abs(movex) > 50 && movetime > 50) {
istouching = true;
isfinish = true;
stopautorotation();
}
break;
case motionevent.action_up:
case motionevent.action_cancel:
if (math.abs(movex) > 50 && movetime > 50) {
istouching = false;
isfinish = true;
if (isfling) {
isfinish = false;
} else {
isfinish = true;
}
restview();
resumeautorotation();
}
break;
}
return super.dispatchtouchevent(event);
}
/**
* 停止自动加载
*/
public void stopautorotation() {
if (mhandler != null && mautorotation) {
mhandler.removemessages(carrouselrotatehandler.mmsgwhat);
}
}
/**
* 从新启动自动加载
*/
public void resumeautorotation() {
if (mhandler != null && mautorotation) {
mhandler.sendemptymessagedelayed(carrouselrotatehandler.mmsgwhat, 1000);
}
}
/**
* 获取所有的view
*
* @return
*/
public list<view> getviews() {
return mcarrouselviews;
}
/**
* 获取角度
*
* @return
*/
public float getangle() {
return mangle;
}
/**
* 设置角度
*
* @param angle
*/
public void setangle(float angle) {
this.mangle = angle;
}
/**
* 获取距离
*
* @return
*/
public float getdistance() {
return mdistance;
}
/**
* 设置距离
*
* @param distance
*/
public void setdistance(float distance) {
this.mdistance = distance;
}
/**
* 获取半径
*
* @return
*/
public float getr() {
return mcarrouselr;
}
/**
* 获取选择是第几个item
*
* @return
*/
public int getselectitem() {
return selectitem;
}
/**
* 设置选中方法
*
* @param selectitem
*/
public void setselectitem(int selectitem) {
if (selectitem >= 0) {
float angle = 0;
if (getselectitem() == 0) {
if (selectitem == mcarrouselviews.size() - 1) {
angle = mangle - (360 / viewcount);
} else {
angle = mangle + (360 / viewcount);
}
} else if (getselectitem() == mcarrouselviews.size() - 1) {
if (selectitem == 0) {
angle = mangle + (360 / viewcount);
} else {
angle = mangle - (360 / viewcount);
}
} else {
if (selectitem > getselectitem()) {
angle = mangle + (360 / viewcount);
} else {
angle = mangle - (360 / viewcount);
}
}
float resultangle = 0;
float part = 360 / viewcount;
if (angle < 0) {
part = -part;
}
//最小角度
float minvalue = (int) (angle / part) * part;
log.e("minvalue", minvalue + "");
//最大角度
float maxvalue = (int) (angle / part) * part;
if (angle >= 0) {//分为是否小于0的情况
if (angle - mlastangle > 0) {
resultangle = maxvalue;
log.e("maxvalue", resultangle + "");
} else {
resultangle = minvalue;
log.e("maxvalue", resultangle + "");
}
} else {
if (angle - mlastangle < 0) {
resultangle = maxvalue;
} else {
resultangle = minvalue;
}
}
if (viewcount > 0) startanimrotation(resultangle, null);
}
}
/**
* 设置半径
*
* @param r
*/
public carrousellayout setr(float r) {
this.mcarrouselr = r;
mdistance = 2f * r;
return this;
}
/**
* 选中回调接口实现
*
* @param moncarrouselitemselectedlistener
*/
public void setoncarrouselitemselectedlistener(oncarrouselitemselectedlistener moncarrouselitemselectedlistener) {
this.moncarrouselitemselectedlistener = moncarrouselitemselectedlistener;
}
/**
* 点击事件回调
*
* @param moncarrouselitemclicklistener
*/
public void setoncarrouselitemclicklistener(oncarrouselitemclicklistener moncarrouselitemclicklistener) {
this.moncarrouselitemclicklistener = moncarrouselitemclicklistener;
}
/**
* 设置是否自动切换
*
* @param autorotation
*/
public carrousellayout setautorotation(boolean autorotation) {
this.mautorotation = autorotation;
mhandler.setautorotation(autorotation);
return this;
}
/**
* 获取自动切换时间
*
* @return
*/
public long getautorotationtime() {
return mhandler.getmrotationtime();
}
/**
* 设置自动切换时间间隔
*
* @param autorotationtime
*/
public carrousellayout setautorotationtime(long autorotationtime) {
if (mhandler != null)
mhandler.setmrotationtime(autorotationtime);
return this;
}
/**
* 是否自动切换
*
* @return
*/
public boolean isautorotation() {
return mautorotation;
}
/**
* 设置自动选择方向
*
* @param mcarrouselrotatedirection
* @return
*/
public carrousellayout setautoscrolldirection(carrouselrotatedirection mcarrouselrotatedirection) {
if (mhandler != null)
mhandler.setmrotatedirection(mcarrouselrotatedirection);
return this;
}
public void createxanimation(int from, int to, boolean start) {
if (xanimation != null) if (xanimation.isrunning() == true) xanimation.cancel();
xanimation = valueanimator.ofint(from, to);
xanimation.addupdatelistener(new valueanimator.animatorupdatelistener() {
@override
public void onanimationupdate(valueanimator animation) {
mrotationx = (integer) animation.getanimatedvalue();
refreshlayout();
}
});
xanimation.setinterpolator(new linearinterpolator());
xanimation.setduration(2000);
if (start) xanimation.start();
}
public valueanimator createzanimation(int from, int to, boolean start) {
if (zanimation != null) if (zanimation.isrunning() == true) zanimation.cancel();
zanimation = valueanimator.ofint(from, to);
zanimation.addupdatelistener(new valueanimator.animatorupdatelistener() {
@override
public void onanimationupdate(valueanimator animation) {
mrotationz = (integer) animation.getanimatedvalue();
refreshlayout();
}
});
zanimation.setinterpolator(new linearinterpolator());
zanimation.setduration(2000);
if (start) zanimation.start();
return zanimation;
}
public carrousellayout setrotationx(int mrotationx) {
this.mrotationx = mrotationx;
return this;
}
public carrousellayout setrotationz(int mrotationz) {
this.mrotationz = mrotationz;
return this;
}
public float getrotationx() {
return mrotationx;
}
public int getrotationz() {
return mrotationz;
}
public valueanimator getrestanimator() {
return restanimator;
}
public valueanimator getanimationr() {
return manimationr;
}
public void setanimationz(valueanimator zanimation) {
this.zanimation = zanimation;
}
public valueanimator getanimationz() {
return zanimation;
}
public void setanimationx(valueanimator xanimation) {
this.xanimation = xanimation;
}
public valueanimator getanimationx() {
return xanimation;
}
}
handler消息机制控制动态及旋转
package com.example.administrator.icome.carrousellayout;
import android.os.handler;
import android.os.message;
/**
* 旋转木马自动旋转控制handler
* created by dalong on 2016/11/12.
*/
public abstract class carrouselrotatehandler extends handler {
//消息what
public static final int mmsgwhat = 1000;
//是否旋转
private boolean isautorotation;
//旋转事件间隔
private long mrotationtime;
//消息对象
private message message;
//旋转方向
private carrouselrotatedirection mrotatedirection;
public carrouselrotatehandler(boolean isautorotation, int mrotationtime , int mrotatedirection) {
this.isautorotation = isautorotation;
this.mrotationtime = mrotationtime;
this.mrotatedirection=mrotatedirection==0?carrouselrotatedirection.clockwise:carrouselrotatedirection.anticlockwise;
message=createmessage();
setautorotation(isautorotation);
}
/**
* 消息处理
* @param msg
*/
@override
public void handlemessage(message msg) {
super.handlemessage(msg);
switch (msg.what){
case mmsgwhat:
//如果自动旋转
if(isautorotation){
//旋转通知
onrotating(mrotatedirection);
//再次发送消息 循环
sendmessage();
}
break;
}
}
/**
* 需要旋转通知方法
*/
public abstract void onrotating(carrouselrotatedirection mrotatedirection);
/**
* 创建消息对象
* @return
*/
private message createmessage(){
message message=new message();
message.what=mmsgwhat;
return message;
}
/**
* 发送消息
*/
public void sendmessage(){
//清除所有mmsgwhat的消息
try {
removemessages(mmsgwhat);
} catch (exception e) {
}
message=createmessage();
this.sendmessagedelayed(message,mrotationtime);
}
/**
* 获取是否自动旋转
* @return
*/
public boolean isautorotation() {
return isautorotation;
}
/**
* 设置是否自动旋转
* @param autorotation
*/
public void setautorotation(boolean autorotation) {
isautorotation = autorotation;
if(autorotation){//如果需要旋转
sendmessage();
}else{//不需要旋转 需要清除所有消息队列中的消息
removemessages(mmsgwhat);
}
}
/**
* 获取旋转事件间隔
* @return
*/
public long getmrotationtime() {
return mrotationtime;
}
/**
* 设置旋转事件间隔
* @param mrotationtime
*/
public void setmrotationtime(long mrotationtime) {
this.mrotationtime = mrotationtime;
}
/**
* 获取旋转方向
* @return
*/
public carrouselrotatedirection getmrotatedirection() {
return mrotatedirection;
}
/**
* 设置旋转方向
* @param mrotatedirection
*/
public void setmrotatedirection(carrouselrotatedirection mrotatedirection) {
this.mrotatedirection = mrotatedirection;
}
}
public class carrousellayout extends relativelayout {
private context mcontext;
//自动旋转 默认不自动
private boolean mautorotation;
//旋转间隔时间 默认设置为2秒
private int mrotationtime;
//旋转木马旋转半径 圆的半径
private float mcarrouselr;
//camera和旋转木马距离
private float mdistance = 2f * mcarrouselr;
//旋转方向 分0顺时针和 1逆时针 俯视旋转木马看
private int mrotatedirection;
//handler
private carrouselrotatehandler mhandler;
//手势处理
private gesturedetector mgesturedetector;
//x旋转
private int mrotationx;
//z旋转
private int mrotationz;
//旋转的角度
private float mangle = 0;
//旋转木马子view
private list<view> mcarrouselviews = new arraylist<>();
//旋转木马子view的数量
private int viewcount;
//半径扩散动画
private valueanimator manimationr;
//记录最后的角度 用来记录上一次取消touch之后的角度
private float mlastangle;
//是否在触摸
private boolean istouching;
//旋转动画
private valueanimator restanimator;
//选中item
private int selectitem;
//item选中回调接口
private oncarrouselitemselectedlistener moncarrouselitemselectedlistener;
//item点击回调接口
private oncarrouselitemclicklistener moncarrouselitemclicklistener;
//x轴旋转动画
private valueanimator xanimation;
//z轴旋转动画
private valueanimator zanimation;
private boolean isfinish = true;//惯性动画是否结束
private boolean isfling;
public carrousellayout(context context) {
this(context, null);
}
public carrousellayout(context context