1. 程式人生 > >可以無限迴圈,自動旋轉,停靠的3D旋轉佈局控制元件

可以無限迴圈,自動旋轉,停靠的3D旋轉佈局控制元件


效果如上圖:

程式碼實現步驟:

1、首先確定這是一個自定義View,再有就是是一個ViewGroup,那麼必須繼承Layout(線性相對都可以)

2、需要收拾操作,要使用GestureDetector(手勢檢測)

3、很明顯選擇是動畫效果,使用ValueAnimation

4、計算旋轉的角度,執行動畫效果

程式碼裡邊都添加了註釋,就不細說了。

下邊就看程式碼:

package com.example.looprotaryswitchlibrary.view;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;

import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Message;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.RelativeLayout;

@SuppressLint("NewApi")
public class LoopRotarySwitchView extends RelativeLayout{
	private Context mContext;//上下文
	private GestureDetector mGestureDetector; //手勢操作
	private float angle;		//旋轉的角度
	private List<View> views = new ArrayList<View>();//子view列表
	private int size; //子控制元件個數
	private OnItemClickListener mItemClickListener;
	private OnItemSelcetListener mItemSelcetListener;
	private OnLoopViewTouchListener mLoopViewTouchListener;
	private boolean isCanCilckListener = false;  //是否可以點選
	private int selectPosition; //現在所選擇的item
	private float last_angle; //最後的角度,用來記錄上一次取消touch之後的角度
	private final static int LoopR = 200;
	private float r =  LoopR;
	private float multiple = .8f; //倍數,view之間的比例
	private float distance = multiple * r;  //子view之間的距離
	private boolean touching = false;//判斷是否手指在touch
	private float distancX ;//在x軸上邊移動的距離
	private float limitX = 30; //滑動的閥值,用來判段能不能點選
	
	private ValueAnimator restAnimator = null;//復位動畫
	private ValueAnimator roAnimator = null;//旋轉動畫
	private boolean isAuto = false;
	private boolean isLeftToRightScroll = true; //預設自動滾動是從右往左
	private AutoScrollHandler mHandler = new AutoScrollHandler(){
		void scroll() {
			if (size != 0) {//判斷自動滑動從那邊開始
				int perAngle = 0;
				if (isLeftToRightScroll) {
					perAngle = 360 /size;
				}else {
					perAngle = -360/size;
				}
				if (angle == 360) {
					angle = 0f;
				}
				AnimRotationTo(angle + perAngle, null);
			}
		};
	};
	/**
	 * 構造方法
	 * @param context
	 * @param attrs
	 * @param defStyle
	 */
	public LoopRotarySwitchView(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);
		init(context);
	}
	/**
	 * 構造方法
	 * @param context
	 * @param attrs
	 */
	public LoopRotarySwitchView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}
	/**
	 * 構造方法
	 * @param context
	 */
	public LoopRotarySwitchView(Context context) {
		super(context);
		init(context);
	}

	public void init(Context context){
		this.mContext = context;
	
		mGestureDetector = new GestureDetector(mContext, getGestureController());//建立手勢物件
	}
	/**
	 * 手勢操作改變angle
	 * @return
	 */
	private GestureDetector.SimpleOnGestureListener getGestureController(){
		
		return new GestureDetector.SimpleOnGestureListener(){
			@Override
			public boolean onScroll(MotionEvent e1, MotionEvent e2,
					float distanceX, float distanceY) {
				angle += distanceX/views.size(); //計算滑動的角度
				initView();
				return true;
			}
		};
	}
	
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		//進行控制元件介面的初始化
		
		initView();
		if (isAuto) {
			mHandler.sendEmptyMessageDelayed(AutoScrollHandler.messageId, mHandler.scroll_interval);
		}
	}
	private void initView() {
		int width = getWidth();
		for (int i = 0; i < views.size(); i++) {
			float x0 = (float)Math.sin(Math.toRadians(angle + 180 - i * 360 / size)) * r;
			float y0 = (float)Math.cos(Math.toRadians(angle + 180 - i * 360 / size)) * r;
			float  scale0 = (distance - y0) / (distance + r);//計運算元view之間的比例,可以看到distance越大的話 比例越小,也就是大小就相差越小
			views.get(i).setScaleX(scale0);//對view進行縮放
			views.get(i).setScaleY(scale0);//對view進行縮放
			views.get(i).setX(width /2 + x0 - views.get(i).getWidth() /2); //設定他的座標
		}
		List<View > arr = new ArrayList<View>();
		for (int i = 0; i < views.size(); i++) {
			arr.add(views.get(i));
			views.get(i).setTag(i);
		}
		
		sortList(arr);
		postInvalidate();
	}
	//對子View 排序,然後根據變化選中是否重繪,這樣是為了實現view 在顯示的時候來控制當前要顯示的是哪三個view,可以改變排序看下效果
	@SuppressWarnings("unchecked")
	private <T> void sortList(List<View> arr) {
		
		@SuppressWarnings("rawtypes")
		Comparator comparator = new SortComparator();
	    T[] array = arr.toArray((T[]) new Object[arr.size()]);
	   
		Arrays.sort(array, comparator);
		 int i = 0;
	        ListIterator<T> it = (ListIterator<T>) arr.listIterator();
	        while (it.hasNext()) {
	            it.next();
	            it.set(array[i++]);
	        }
		for (int j = 0; j < arr.size(); j++) {
			arr.get(j).bringToFront();
		}
	}
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		//對容器中的子view放置位置
		if (changed) {
			checkChildView();
			
			if (mItemSelcetListener != null) {
				isCanCilckListener = true;
				mItemSelcetListener.onSelect(selectPosition, views.get(selectPosition));
			}
			Ranimation();//子view初始化動畫
		}
	}
	public void Ranimation(){
		Ranimation(1f, r);
	}
	public void Ranimation(boolean fromZeroToR){
		if (fromZeroToR) {
			Ranimation(1f, LoopR);
		}else {
			Ranimation(LoopR, 1f);
		}
	}
	public void Ranimation(float from, float to) {
		roAnimator = ValueAnimator.ofFloat(from,to);
		roAnimator.addUpdateListener(new AnimatorUpdateListener() {
			
			@Override
			public void onAnimationUpdate(ValueAnimator animation) {
				r = (Float)animation.getAnimatedValue();
				initView();
			}
		});
		
		roAnimator.setInterpolator(new DecelerateInterpolator());
		roAnimator.setDuration(2000);
		roAnimator.start();
		
	}
	public void checkChildView(){
		for (int i = 0; i < views.size(); i++) {//先清空views裡邊可能存在的view防止重複
			views.remove(i);
		}
		final int count = getChildCount(); //獲取子View的個數
		size = count;
		
		for (int i = 0; i < count; i++) {
			View view = getChildAt(i); //獲取指定的子view
			final int position = i;
			views.add(view);
			view.setOnClickListener(new OnClickListener() {
				
				@Override
				public void onClick(View v) {
					//對子view新增點選事件
					if (position != calculateItem()) {//
						setSelectItem(position);;
					}else {
						if (isCanCilckListener && mItemClickListener != null) {
							mItemClickListener.onItemClick(position, views.get(position));
						}
					}
				}
			});
			
		}
		
	}
	/**
	 * 設定所選中的item
	 * @param selectItem
	 */
	public void setSelectItem(int selectItem){
		if (selectItem >= 0) {
			float corner = 0;
			if (getSelectPosition() == 0) {
				if (selectItem == views.size() - 1) {
					corner = angle - (360 / size);
				}else {
					corner = angle +(360 / size);
				}
			}else if (getSelectPosition() == views.size() - 1) {
				if (selectItem == 0) {
					corner = angle + (360 / size);
				}else {
					corner = angle - (360 /size);
				}
			}else {
				
				if (selectItem > getSelectPosition()) {
					corner = angle +(360 /size);
				}else {
					corner = angle - (360 /size);
				}
			}
			
			float position = 0 ;
			float per = 360 /size;
			if (corner < 0) {
				per = -per;
			}
			
			float minValue = (int)(corner /per) * per;
			float maxValue = (int)(corner / per) * per;
			if (corner >= 0) {
				if (corner - last_angle > 0) {
					position = maxValue;
				}else {
					position = minValue;
				}
			}else {
				if (corner - last_angle < 0) {
					position = maxValue;
				}else {
					position = minValue;
				}
			}
			if (size > 0) {//旋轉動畫
				AnimRotationTo(position, null);
			}
		}
	}
	public float getAngle() {
		return angle;
	}
	public void setAngle(float angle) {
		this.angle = angle;
	}
	public void setmItemClickListener(OnItemClickListener mItemClickListener) {
		this.mItemClickListener = mItemClickListener;
	}
	public void setmItemSelcetListener(OnItemSelcetListener mItemSelcetListener) {
		this.mItemSelcetListener = mItemSelcetListener;
	}
	public void setmLoopViewTouchListener(
			OnLoopViewTouchListener mLoopViewTouchListener) {
		this.mLoopViewTouchListener = mLoopViewTouchListener;
	}
	/**
	 * 計算現在選中item
	 * @return
	 */
	public int calculateItem(){
		return (int)(angle / ( 360 / size)) % size;
	}

	public int getSelectPosition() {
		return selectPosition;
	}
	
	public float getR() {
		return r;
	}
	public LoopRotarySwitchView setR(float r) {
		this.r = r;
		distance = multiple * r;
		return this;
		
	}
	private  boolean onTouch(MotionEvent event){
		
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			last_angle = angle ;
			touching = true;
		}
		boolean sc = mGestureDetector.onTouchEvent(event);
		if (sc) {
			this.getParent().requestDisallowInterceptTouchEvent(true);
		}
		if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
			touching = false;
			restPosition();//重置
			return true;
			
		}
		return true;
	}
	
	private void restPosition() {
		if (size == 0) {
			return ;
		}
		
		float position = 0;
		float per = 360 /size;
		if (angle < 0) {
			per = -per;
		}
		
		float minValue = (int)(angle / per) * per;
		float maxValue = (int)(angle / per) * per + per;
		if (angle > 0) {
			if (angle -last_angle > 0) {
				position = maxValue;
			}else {
				position = minValue;
			}
			
		}else {
			if (angle - last_angle > 0) {
				position  = maxValue;
			}else {
				position = minValue;
			}
		}
		
		AnimRotationTo(position,null);
	}
	/**
	 * 動畫
	 * @param position
	 * @param object
	 */
	private void AnimRotationTo(float position, final Runnable runnable) {
		if (angle == position) {//如果相同說明不需要旋轉
			return;
		}
		restAnimator = ValueAnimator.ofFloat(angle,position);
		restAnimator.setInterpolator(new DecelerateInterpolator());//設定旋轉減速插值器
		restAnimator.setDuration(300);
		restAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
			
			@Override
			public void onAnimationUpdate(ValueAnimator animation) {
				if (touching == false) {
					angle = (Float)animation.getAnimatedValue();
					initView();
				}
			}
		});
		
		restAnimator.addListener(new AnimatorListener() {
			
			@Override
			public void onAnimationStart(Animator animation) {
				
			}
			
			@Override
			public void onAnimationRepeat(Animator animation) {
				
			}
			
			@Override
			public void onAnimationEnd(Animator animation) {
				if (touching == false) {
					selectPosition = calculateItem();
				}
				if (mItemSelcetListener != null) {
					mItemSelcetListener.onSelect(selectPosition, views.get(selectPosition));
				}
			}
			
			@Override
			public void onAnimationCancel(Animator animation) {
				
			}
		});
		
		if (runnable != null) {
			restAnimator.addListener(new AnimatorListener() {
				
				@Override
				public void onAnimationStart(Animator animation) {
					
				}
				
				@Override
				public void onAnimationRepeat(Animator animation) {
					
				}
				
				@Override
				public void onAnimationEnd(Animator animation) {
					runnable.run();
				}
				
				@Override
				public void onAnimationCancel(Animator animation) {
					// TODO Auto-generated method stub
					
				}
			});
		}
		restAnimator.start();
		
	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (mLoopViewTouchListener != null) {
			mLoopViewTouchListener.onTouch(event);
		}
		isCanClickListener(event);
		return true;
	}
	
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		onTouch(ev);
		if (mLoopViewTouchListener != null) {
			mLoopViewTouchListener.onTouch(ev);
		}
		isCanClickListener(ev);
		return super.dispatchTouchEvent(ev);
	}
	
	public void isCanClickListener(MotionEvent event){
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			distancX = event.getX();
			if (isAuto) {
				mHandler.removeMessages(AutoScrollHandler.messageId);
			}
			
			break;
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			if (isAuto) {
				mHandler.sendEmptyMessageDelayed(AutoScrollHandler.messageId,  mHandler.scroll_interval);
			}
			if (event.getX() - distancX > limitX || distancX - event.getX() > limitX) {
				isCanCilckListener = false;
			}else{
				isCanCilckListener = true;
			}
			break;
		case MotionEvent.ACTION_MOVE:
			
			break;

		default:
			break;
		}
		
	}
	/**
	 * 自動滾動
	 */
	public void AutoScroll() {
		angle  = angle + (360/size);
		initView();
	}
	public boolean isAuto() {
		return isAuto;
	}
	public void setAuto(boolean isAuto) {
		this.isAuto = isAuto;
	}
	public boolean isLeftScroll() {
		return isLeftToRightScroll;
	}
	public void setLeftScroll(boolean isLeftToRightScroll) {
		this.isLeftToRightScroll = isLeftToRightScroll;
	}
	
	public void setScrollInterval(long time){
		if (mHandler != null) {
			mHandler.setScroll_interval(time);
		}
	}
}
</pre><pre code_snippet_id="1651394" snippet_file_name="blog_20160418_3_3380648" name="code" class="java">

監聽器介面類:

package com.example.looprotaryswitchlibrary.view;

import android.view.View;

public interface OnItemClickListener {
	void onItemClick(int position,View view);
}

package com.example.looprotaryswitchlibrary.view;

import android.view.View;

public interface OnItemSelcetListener {
	void onSelect(int position, View View);
}

package com.example.looprotaryswitchlibrary.view;

import android.view.MotionEvent;

public interface OnLoopViewTouchListener {
	void onTouch(MotionEvent event);
}
自定義排序演算法
package com.example.looprotaryswitchlibrary.view;

import java.util.Comparator;
import android.annotation.SuppressLint;
import android.view.View;

public class SortComparator implements Comparator<View>{
	
	@SuppressLint("NewApi")
	@Override
	public int compare(View lhs, View rhs) {
		int result = 0;
		
		result = (int)(1000 * lhs.getScaleX() - 1000 * rhs.getScaleX());
		return result;
	}

}
自動滑動Handler回撥:
package com.example.looprotaryswitchlibrary.view;

import android.os.Handler;
import android.os.Message;

public abstract class AutoScrollHandler extends Handler{
	
	 long  scroll_interval = 3000;//自動滾動時間間隔,預設值
	final static int messageId = 1000;
	@Override
	public void handleMessage(Message msg) {
		// TODO Auto-generated method stub
		
		int what = msg.what;
		switch (what) {
		case messageId:
			scroll();
			sendMessage();
			break;

		default:
			break;
		}
		super.handleMessage(msg);
	}
	public long getScroll_interval() {
		return scroll_interval;
	}
	public void setScroll_interval(long scroll_interval) {
		this.scroll_interval = scroll_interval;
	}

	public void sendMessage(){
		removeMessages(messageId);
		sendEmptyMessageDelayed(messageId,scroll_interval);
	}
	abstract void scroll();

}


主要程式碼就是這麼多。