自定义安卓图片轮播控件


在安卓开发中,图片轮播算是比较常用的控件。网上有很多人的各种各样的实现,比如有基于ViewPager的,这种实现总让人觉得太大材小用了,而且算不上是轮播,应该滚动到最后一页是没法再后翻的,这个时候会考虑两种补救措施:一种是直接smooth到第一次;另一种是后面新增和一个第一页一模一样的页面,这样就可以偷偷地在切换到最后一页时再执行smooth到第一次的操作,但这样可能需要自定义indicator指示器类,。。。。头晕。。。。 也有其他人是基于ViewFlipper,但别人的总是没有自己的好,于是下面是我的实现。


效果图:

超过200k无法上传!


实现的文件介绍:


src

    |- me.lanfog.myandroid.widget

        |- ImageFlipper            轮播控件类:支持触摸触发前后翻,默认自动从右向左轮翻

        |- CycleIndicator            一个指示器:一排圆点,选中为实心,未选中为空心

        |- Activity.java            测试示例:视图

|- res

    |- anim

        |- slide_in_left.xml                左边进入动画

        |- slide_out_right.xml            右边出去动画

        |- slide_in_right.xml            右边进入动画

        |- slide_out_left.xml             左边出去动画

    |- layout

            |- layout.xml            测试实例 :布局文件


各个文件内容如下:


ImageFlipper.java

package me.lanfog.myandroid.widget;

import me.lanfog.myandroid.R;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.ViewFlipper;

public class ImageFlipper extends ViewFlipper {

	private Animation previousInAnimation, previousOutAnimation; // 向前切换时,进出动画
	private Animation nextInAnimation, nextOutAnimation; // 前后切换时,进出动画
	private boolean animRunning;
	private AnimationListener mAnimationListener =  new AnimationListener() {
		
		@Override
		public void onAnimationStart(Animation animation) {
			// TODO Auto-generated method stub
			animRunning  = true;
		}
		
		@Override
		public void onAnimationRepeat(Animation animation) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onAnimationEnd(Animation animation) {
			// TODO Auto-generated method stub
			animRunning = false;
			
			if(mOnPageChangeListener != null)
				mOnPageChangeListener.onPageSelected(indexOfChild(getCurrentView()));
		}
	};
	private float firstX, deltaX;
	private int flipSpacing = 100; // 触摸触发切换,生效距离
	private OnPageChangeListener mOnPageChangeListener; // 切换事件监听器
	
	public ImageFlipper(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		init();
	}

	public ImageFlipper(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
		init();
	}

	private void init(){
		
		// 切换动画效果
		setAnimation(R.anim.slide_in_left, R.anim.slide_out_right, R.anim.slide_in_right, R.anim.slide_out_left);
		// 切换间隔
		setFlipInterval(2000);
		// 自动开始
		setAutoStart(true);
	}
	
	@Override
	public void showPrevious() {
		
		setInAnimation(previousInAnimation);
		setOutAnimation(previousOutAnimation);
		super.showPrevious();
	}

	@Override
	public void showNext() {
		
		setInAnimation(nextInAnimation);
		setOutAnimation(nextOutAnimation);
		super.showNext();
	}

	public void setViews(View[] views){
		
		for(View v:views)
			addView(v, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
	}
	
	public void setViews(int[] ids){
		
		removeAllViews();
		for(int id:ids){
			addView(id);
		}
	}
	
	public void addView(int id) {
		
		ImageView iv = new ImageView(getContext());
		iv.setImageResource(id);
		iv.setScaleType(ImageView.ScaleType.FIT_XY);
		addView(iv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
	}
	
	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		
		// 动画过程中,不响应触摸操作
		if(animRunning)
			return true;
		
		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			
			// 暂停自动切换
			if(isFlipping())
				stopFlipping();
			
			firstX = ev.getX();
			break;
		case MotionEvent.ACTION_UP:
			
			deltaX = ev.getX() - firstX;
			if(Math.abs(deltaX) > flipSpacing){
				
				if(deltaX > 0)
					this.showPrevious();
				else {
					this.showNext();
					
					// 启动自动切换
					// 起落间隔距离不足时,之后不会再有自动切换
					this.startFlipping();
				}
			}
			
			break;
		}
		
		return true;
	}

	public void setAnimation(Animation pin, Animation pout, Animation nin, Animation nout){
		this.previousInAnimation = pin;
		this.previousOutAnimation = pout;
		this.nextInAnimation = nin;
		this.nextOutAnimation = nout;
		
		this.previousInAnimation.setAnimationListener(mAnimationListener);
		this.nextInAnimation.setAnimationListener(mAnimationListener);
	}
	
	public void setAnimation(int pin, int pout, int nin, int nout){
		this.setAnimation(
				AnimationUtils.loadAnimation(getContext(), pin),
				AnimationUtils.loadAnimation(getContext(), pout),
				AnimationUtils.loadAnimation(getContext(), nin),
				AnimationUtils.loadAnimation(getContext(), nout));
	}

	public OnPageChangeListener getOnPageChangeListener() {
		return mOnPageChangeListener;
	}

	public void setOnPageChangeListener(OnPageChangeListener mOnPageChangeListener) {
		this.mOnPageChangeListener = mOnPageChangeListener;
	}
	
	/**
	 * 页面切换监听器
	 */
	public static interface OnPageChangeListener {
		public void onPageSelected(int index);
	}
}

其中,默认会使用到四个动画效果:

slide_in_left.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromXDelta="-100%p"
    android:toXDelta="0"/>


slide_out_right.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromXDelta="0"
    android:toXDelta="100%p"/>


slide_in_right.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromXDelta="100%p"
    android:toXDelta="0" />


slide_out_left.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromXDelta="0"
    android:toXDelta="-100%p" />


为了好看一点,再实现一个指示器类:

CycleIndicator

package me.lanfog.myandroid.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

public class CycleIndicator extends View {

	private int pageCount; // 总页数
	
	private int pageSelected; // 当前页
	
	private int radius = 5; // 半径
	
	private int stokenWidth = 2; // 边框宽度
	
	private int gap = 3; // 间隔
	
	private Paint mPaint;

	
	public CycleIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		// TODO Auto-generated constructor stub
		init();
	}

	public CycleIndicator(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		init();
	}

	public CycleIndicator(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
		init();
	}

	private void init() {
		
		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mPaint.setStrokeWidth(stokenWidth);
		mPaint.setColor(Color.WHITE);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		setMeasuredDimension(measureWidth(), measureHeight());
	}

	protected int measureWidth() {
		return ( radius + stokenWidth ) * 2 * pageCount + gap * ( pageCount - 1 );
	}

	protected int measureHeight() {
		return ( radius + stokenWidth ) * 2;
	}
	
	@Override
	protected void onDraw(Canvas canvas) {
		drawIndicator(canvas);
	}
	
	private void drawIndicator(Canvas canvas) {
		
		for(int i=0;i<pageCount;i++) {
			
			if(i == pageSelected)
				mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
			else
				mPaint.setStyle(Paint.Style.STROKE);
			
			canvas.drawCircle(( radius + stokenWidth ) * ( 2 * i + 1 ) + gap * i , radius + stokenWidth , radius, mPaint); 
		}
	}
	
	public void setPageCount(int count){
		this.pageCount = count;
		this.requestLayout();
	}
	
	public void onPageSelected(int index){
		this.pageSelected = index;
		this.postInvalidate();
	}

	public int getRadius() {
		return radius;
	}

	public void setRadius(int radius) {
		this.radius = radius;
	}

	public int getStokenWidth() {
		return stokenWidth;
	}

	public void setStokenWidth(int stokenWidth) {
		this.stokenWidth = stokenWidth;
	}

	public int getGap() {
		return gap;
	}

	public void setGap(int gap) {
		this.gap = gap;
	}
	
	
}


以下是测试类:

activity.xml

<LinearLayout 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"
    android:background="@android:color/holo_green_dark"
    android:orientation="vertical" >

    <RelativeLayout 
        android:layout_width="match_parent"
        android:layout_height="100dp">
	    
	    <me.lanfog.myandroid.widget.ImageFlipper
	        android:id="@+id/flipper0"
	        android:layout_width="match_parent"
	        android:layout_height="100dp" />
	
	    <me.lanfog.myandroid.widget.CycleIndicator
	        android:id="@+id/indicator0"
	        android:layout_centerHorizontal="true"
	        android:layout_alignParentBottom="true"
	        android:layout_marginBottom="10dp"
	        android:layout_width="wrap_content"
	        android:layout_height="wrap_content" />
        
    </RelativeLayout>
    
    <FrameLayout
        android:layout_weight="1"
        android:layout_width="match_parent"
        android:background="@android:color/white"
        android:layout_height="0dp"/>

</LinearLayout>


Activity.java

final CycleIndicator indicator0 = (CycleIndicator) findViewById(R.id.indicator0);
		final ImageFlipper flipper0 = (ImageFlipper) findViewById(R.id.flipper0);
		flipper0.setViews(new int[]{R.drawable.p0, R.drawable.p1, R.drawable.p2});
		indicator0.setPageCount(3);
		flipper0.setOnPageChangeListener(new ImageFlipper.OnPageChangeListener() {
			
			@Override
			public void onPageSelected(int index) {
				indicator0.onPageSelected(index);
				Toast.makeText(FlipperDemo.this, "index=" + index, 20).show();
			}
		});






其他动画切换效果:


上下翻动:

slide_in_top.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromYDelta="-100%p"
    android:toYDelta="0"/>

slide_out_bottom.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromYDelta="0"
    android:toYDelta="100%p"/>

slide_in_bottom.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromYDelta="100%p"
    android:toYDelta="0" />

slide_out_top.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromYDelta="0"
    android:toYDelta="-100%p" />


flipper0.setAnimation(R.anim.slide_in_bottom, R.anim.slide_out_top, R.anim.slide_in_top, R.anim.slide_out_bottom);


淡入淡出:

fade_in.xml

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromAlpha="0"
    android:toAlpha="1" />

fade_out.xml

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromAlpha="1"
    android:toAlpha="0" />


flipper0.setAnimation(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);



总结:


测试时候比较基于ViewPager和ViewFlipper两种实现方式发现,除了之前是否支持轮放这一特征之外,

最大的一个区别是:前者存在中间状态,而后者没有,即在触摸滑动过程中,前者会存在两张图片会同时静态地存在,而后则会仅在动画过程中才有。对此,一个可能完善的逻辑是:修正触摸事件的处理,在move过程中,判断移动距离达到指定值,则触发切换,如下:

            switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			
			// 暂停自动切换
			if(isFlipping())
				stopFlipping();
			
			firstX = ev.getX();
			break;
		case MotionEvent.ACTION_MOVE:
			
			if(firstX == -1)
				break;
			
			deltaX = ev.getX() - firstX;
			if(Math.abs(deltaX) > flipSpacing){
				
				if(deltaX > 0)
					this.showPrevious();
				else {
					this.showNext();
					
					// 成功触后翻之后,刽自动后翻
					this.startFlipping();
				}
				
				firstX = -1;
			}
			
			break;
		}



你可能感兴趣的:(安卓,图片轮播)