1. 程式人生 > >Android高階圖片滾動控制元件,編寫3D版的圖片輪播器

Android高階圖片滾動控制元件,編寫3D版的圖片輪播器

大家好,好久不見了,最近由於工作特別繁忙,已經有一個多月的時間沒寫部落格了,我也是深感慚愧。那麼今天的這篇既然是闊別了一個多月的文章,當然要帶來更加給力點的內容了,那麼話不多說,趕快進入到今天的正題吧。

說到圖片輪播器,很多的Android應用中都會帶有這個功能,比如說網易新聞、淘寶等。最新我們公司的一款應用也加入了這個功能,並且在圖片輪播的基礎上還增加了三維立體的效果,但比較遺憾的是,整體效果並不理想,使用者體驗性比較糟糕。因此,我就花了點時間去編寫了一個效果更好的3D圖片輪播器,自我感覺還是比較滿意的,這裡果斷寫一篇部落格來分享給大家。

首先來介紹一下實現原理吧,傳統的圖片輪播器在一個介面上只會顯示一張圖片,要用手指進行左右滑動才能看到其它的圖片。這裡我們將思維發散一下,允許在一個介面上同時顯示三張圖片,再通過Camera的方式對左右的兩張圖進行3D旋轉,這樣就能製作出一種立體的圖片輪播器了,原理示意圖如下所示:


對圖片進行立體操作還是要使用到Camera技術,如果你對這個技術還不太熟悉,可以到網上搜一些相關資料,或者參考我前面的一篇文章:Android中軸旋轉特效實現,製作別樣的圖片瀏覽器

那麼我們現在就開始動手吧,首先新建一個Android專案,起名叫做ImageSwitchViewTest。

然後新建一個Image3DView繼承自ImageView,它會繼承ImageView的所有屬性,並且加入3D旋轉的功能,程式碼如下所示:

public class Image3DView extends ImageView {
	/**
	 * 旋轉角度的基準值
	 */
	private static final float BASE_DEGREE = 50f;
	/**
	 * 旋轉深度的基準值
	 */
	private static final float BASE_DEEP = 150f;
	private Camera mCamera;
	private Matrix mMaxtrix;
	private Bitmap mBitmap;
	/**
	 * 當前圖片對應的下標
	 */
	private int mIndex;
	/**
	 * 在前圖片在X軸方向滾動的距離
	 */
	private int mScrollX;
	/**
	 * Image3DSwitchView控制元件的寬度
	 */
	private int mLayoutWidth;
	/**
	 * 當前圖片的寬度
	 */
	private int mWidth;
	/**
	 * 當前旋轉的角度
	 */
	private float mRotateDegree;
	/**
	 * 旋轉的中心點
	 */
	private float mDx;
	/**
	 * 旋轉的深度
	 */
	private float mDeep;

	public Image3DView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mCamera = new Camera();
		mMaxtrix = new Matrix();
	}

	/**
	 * 初始化Image3DView所需要的資訊,包括圖片寬度,擷取背景圖等。
	 */
	public void initImageViewBitmap() {
		if (mBitmap == null) {
			setDrawingCacheEnabled(true);
			buildDrawingCache();
			mBitmap = getDrawingCache();
		}
		mLayoutWidth = Image3DSwitchView.mWidth;
		mWidth = getWidth() + Image3DSwitchView.IMAGE_PADDING * 2;
	}

	/**
	 * 設定旋轉角度。
	 * 
	 * @param index
	 *            當前圖片的下標
	 * @param scrollX
	 *            當前圖片在X軸方向滾動的距離
	 */
	public void setRotateData(int index, int scrollX) {
		mIndex = index;
		mScrollX = scrollX;
	}

	/**
	 * 回收當前的Bitmap物件,以釋放記憶體。
	 */
	public void recycleBitmap() {
		if (mBitmap != null && !mBitmap.isRecycled()) {
			mBitmap.recycle();
		}
	}

	@Override
	public void setImageResource(int resId) {
		super.setImageResource(resId);
		mBitmap = null;
		initImageViewBitmap();
	}

	@Override
	public void setImageBitmap(Bitmap bm) {
		super.setImageBitmap(bm);
		mBitmap = null;
		initImageViewBitmap();
	}

	@Override
	public void setImageDrawable(Drawable drawable) {
		super.setImageDrawable(drawable);
		mBitmap = null;
		initImageViewBitmap();
	}

	@Override
	public void setImageURI(Uri uri) {
		super.setImageURI(uri);
		mBitmap = null;
		initImageViewBitmap();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		if (mBitmap == null) {
			// 如果Bitmap物件還不存在,先使用父類的onDraw方法進行繪製
			super.onDraw(canvas);
		} else {
			if (isImageVisible()) {
				// 繪圖時需要注意,只有當圖片可見的時候才進行繪製,這樣可以節省運算效率
				computeRotateData();
				mCamera.save();
				mCamera.translate(0.0f, 0.0f, mDeep);
				mCamera.rotateY(mRotateDegree);
				mCamera.getMatrix(mMaxtrix);
				mCamera.restore();
				mMaxtrix.preTranslate(-mDx, -getHeight() / 2);
				mMaxtrix.postTranslate(mDx, getHeight() / 2);
				canvas.drawBitmap(mBitmap, mMaxtrix, null);
			}
		}
	}

	/**
	 * 在這裡計算所有旋轉所需要的資料。
	 */
	private void computeRotateData() {
		float degreePerPix = BASE_DEGREE / mWidth;
		float deepPerPix = BASE_DEEP / ((mLayoutWidth - mWidth) / 2);
		switch (mIndex) {
		case 0:
			mDx = mWidth;
			mRotateDegree = 360f - (2 * mWidth + mScrollX) * degreePerPix;
			if (mScrollX < -mWidth) {
				mDeep = 0;
			} else {
				mDeep = (mWidth + mScrollX) * deepPerPix;
			}
			break;
		case 1:
			if (mScrollX > 0) {
				mDx = mWidth;
				mRotateDegree = (360f - BASE_DEGREE) - mScrollX * degreePerPix;
				mDeep = mScrollX * deepPerPix;
			} else {
				if (mScrollX < -mWidth) {
					mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
					mRotateDegree = (-mScrollX - mWidth) * degreePerPix;
				} else {
					mDx = mWidth;
					mRotateDegree = 360f - (mWidth + mScrollX) * degreePerPix;
				}
				mDeep = 0;
			}
			break;
		case 2:
			if (mScrollX > 0) {
				mDx = mWidth;
				mRotateDegree = 360f - mScrollX * degreePerPix;
				mDeep = 0;
				if (mScrollX > mWidth) {
					mDeep = (mScrollX - mWidth) * deepPerPix;
				}
			} else {
				mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
				mRotateDegree = -mScrollX * degreePerPix;
				mDeep = 0;
				if (mScrollX < -mWidth) {
					mDeep = -(mWidth + mScrollX) * deepPerPix;
				}
			}
			break;
		case 3:
			if (mScrollX < 0) {
				mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
				mRotateDegree = BASE_DEGREE - mScrollX * degreePerPix;
				mDeep = -mScrollX * deepPerPix;
			} else {
				if (mScrollX > mWidth) {
					mDx = mWidth;
					mRotateDegree = 360f - (mScrollX - mWidth) * degreePerPix;
				} else {
					mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
					mRotateDegree = BASE_DEGREE - mScrollX * degreePerPix;
				}
				mDeep = 0;
			}
			break;
		case 4:
			mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
			mRotateDegree = (2 * mWidth - mScrollX) * degreePerPix;
			if (mScrollX > mWidth) {
				mDeep = 0;
			} else {
				mDeep = (mWidth - mScrollX) * deepPerPix;
			}
			break;
		}
	}

	/**
	 * 判斷當前圖片是否可見。
	 * 
	 * @return 當前圖片可見返回true,不可見返回false。
	 */
	private boolean isImageVisible() {
		boolean isVisible = false;
		switch (mIndex) {
		case 0:
			if (mScrollX < (mLayoutWidth - mWidth) / 2 - mWidth) {
				isVisible = true;
			} else {
				isVisible = false;
			}
			break;
		case 1:
			if (mScrollX > (mLayoutWidth - mWidth) / 2) {
				isVisible = false;
			} else {
				isVisible = true;
			}
			break;
		case 2:
			if (mScrollX > mLayoutWidth / 2 + mWidth / 2
					|| mScrollX < -mLayoutWidth / 2 - mWidth / 2) {
				isVisible = false;
			} else {
				isVisible = true;
			}
			break;
		case 3:
			if (mScrollX < -(mLayoutWidth - mWidth) / 2) {
				isVisible = false;
			} else {
				isVisible = true;
			}
			break;
		case 4:
			if (mScrollX > mWidth - (mLayoutWidth - mWidth) / 2) {
				isVisible = true;
			} else {
				isVisible = false;
			}
			break;
		}
		return isVisible;
	}

}

這段程式碼比較長,也比較複雜的,我們慢慢來分析。在Image3DView的建構函式中初始化了一個Camera和Matrix物件,用於在後面對圖片進行3D操作。然後在initImageViewBitmap()方法中初始化了一些必要的資訊,比如對當前圖片進行截圖,以用於後續的立體操作,得到當前圖片的寬度等。

然後還提供了一個setRotateData()方法,用於設定當前圖片的下標和滾動距離,有了這兩樣資料就可以通過computeRotateData()方法來計算旋轉角度的一些資料,以及通過isImageVisible()方法來判斷出當前圖片是否可見了,具體詳細的演算法邏輯你可以閱讀程式碼來慢慢分析。

接下來當圖片需要繪製到螢幕上的時候就會呼叫onDraw()方法,在onDraw()方法中會進行判斷,如果當前圖片可見就呼叫computeRotateData()方法來計算旋轉時所需要的各種資料,之後再通過Camera和Matrix來執行旋轉操作就可以了。

接著新建一個Image3DSwitchView繼承自ViewGroup,程式碼如下所示:

public class Image3DSwitchView extends ViewGroup {

	/**
	 * 圖片左右兩邊的空白間距
	 */
	public static final int IMAGE_PADDING = 10;
	private static final int TOUCH_STATE_REST = 0;
	private static final int TOUCH_STATE_SCROLLING = 1;
	/**
	 * 滾動到下一張圖片的速度
	 */
	private static final int SNAP_VELOCITY = 600;
	/**
	 * 表示滾動到下一張圖片這個動作
	 */
	private static final int SCROLL_NEXT = 0;
	/**
	 * 表示滾動到上一張圖片這個動作
	 */
	private static final int SCROLL_PREVIOUS = 1;
	/**
	 * 表示滾動回原圖片這個動作
	 */
	private static final int SCROLL_BACK = 2;
	private static Handler handler = new Handler();
	/**
	 * 控制元件寬度
	 */
	public static int mWidth;
	private VelocityTracker mVelocityTracker;
	private Scroller mScroller;
	/**
	 * 圖片滾動監聽器,當圖片發生滾動時回撥這個介面
	 */
	private OnImageSwitchListener mListener;
	/**
	 * 記錄當前的觸控狀態
	 */
	private int mTouchState = TOUCH_STATE_REST;
	/**
	 * 記錄被判定為滾動運動的最小滾動值
	 */
	private int mTouchSlop;
	/**
	 * 記錄控制元件高度
	 */
	private int mHeight;
	/**
	 * 記錄每張圖片的寬度
	 */
	private int mImageWidth;
	/**
	 * 記錄圖片的總數量
	 */
	private int mCount;
	/**
	 * 記錄當前顯示圖片的座標
	 */
	private int mCurrentImage;
	/**
	 * 記錄上次觸控的橫座標值
	 */
	private float mLastMotionX;
	/**
	 * 是否強制重新佈局
	 */
	private boolean forceToRelayout;
	private int[] mItems;

	public Image3DSwitchView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
		mScroller = new Scroller(context);
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		if (changed || forceToRelayout) {
			mCount = getChildCount();
			// 圖片數量必須大於5,不然無法正常顯示
			if (mCount < 5) {
				return;
			}
			mWidth = getMeasuredWidth();
			mHeight = getMeasuredHeight();
			// 每張圖片的寬度設定為控制元件寬度的百分之六十
			mImageWidth = (int) (mWidth * 0.6);
			if (mCurrentImage >= 0 && mCurrentImage < mCount) {
				mScroller.abortAnimation();
				setScrollX(0);
				int left = -mImageWidth * 2 + (mWidth - mImageWidth) / 2;
				// 分別獲取每個位置上應該顯示的圖片下標
				int[] items = { getIndexForItem(1), getIndexForItem(2),
						getIndexForItem(3), getIndexForItem(4),
						getIndexForItem(5) };
				mItems = items;
				// 通過迴圈為每張圖片設定位置
				for (int i = 0; i < items.length; i++) {
					Image3DView childView = (Image3DView) getChildAt(items[i]);
					childView.layout(left + IMAGE_PADDING, 0, left
							+ mImageWidth - IMAGE_PADDING, mHeight);
					childView.initImageViewBitmap();
					left = left + mImageWidth;
				}
				refreshImageShowing();
			}
			forceToRelayout = false;
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (mScroller.isFinished()) {
			if (mVelocityTracker == null) {
				mVelocityTracker = VelocityTracker.obtain();
			}
			mVelocityTracker.addMovement(event);
			int action = event.getAction();
			float x = event.getX();
			switch (action) {
			case MotionEvent.ACTION_DOWN:
				// 記錄按下時的橫座標
				mLastMotionX = x;
				break;
			case MotionEvent.ACTION_MOVE:
				int disX = (int) (mLastMotionX - x);
				mLastMotionX = x;
				scrollBy(disX, 0);
				// 當發生移動時重新整理圖片的顯示狀態
				refreshImageShowing();
				break;
			case MotionEvent.ACTION_UP:
				mVelocityTracker.computeCurrentVelocity(1000);
				int velocityX = (int) mVelocityTracker.getXVelocity();
				if (shouldScrollToNext(velocityX)) {
					// 滾動到下一張圖
					scrollToNext();
				} else if (shouldScrollToPrevious(velocityX)) {
					// 滾動到上一張圖
					scrollToPrevious();
				} else {
					// 滾動回當前圖片
					scrollBack();
				}
				if (mVelocityTracker != null) {
					mVelocityTracker.recycle();
					mVelocityTracker = null;
				}
				break;
			}
		}
		return true;
	}

	/**
	 * 根據當前的觸控狀態來決定是否遮蔽子控制元件的互動能力。
	 */
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		int action = ev.getAction();
		if ((action == MotionEvent.ACTION_MOVE)
				&& (mTouchState != TOUCH_STATE_REST)) {
			return true;
		}
		float x = ev.getX();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			mLastMotionX = x;
			mTouchState = TOUCH_STATE_REST;
			break;
		case MotionEvent.ACTION_MOVE:
			int xDiff = (int) Math.abs(mLastMotionX - x);
			if (xDiff > mTouchSlop) {
				mTouchState = TOUCH_STATE_SCROLLING;
			}
			break;
		case MotionEvent.ACTION_UP:
		default:
			mTouchState = TOUCH_STATE_REST;
			break;
		}
		return mTouchState != TOUCH_STATE_REST;
	}

	@Override
	public void computeScroll() {
		if (mScroller.computeScrollOffset()) {
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			refreshImageShowing();
			postInvalidate();
		}
	}

	/**
	 * 設定圖片滾動的監聽器,每當有圖片滾動時會回撥此介面。
	 * 
	 * @param listener
	 *            圖片滾動監聽器
	 */
	public void setOnImageSwitchListener(OnImageSwitchListener listener) {
		mListener = listener;
	}

	/**
	 * 設定當前顯示圖片的下標,注意如果該值小於零或大於等於圖片的總數量,圖片則無法正常顯示。
	 * 
	 * @param currentImage
	 *            圖片的下標
	 */
	public void setCurrentImage(int currentImage) {
		mCurrentImage = currentImage;
		requestLayout();
	}

	/**
	 * 滾動到下一張圖片。
	 */
	public void scrollToNext() {
		if (mScroller.isFinished()) {
			int disX = mImageWidth - getScrollX();
			checkImageSwitchBorder(SCROLL_NEXT);
			if (mListener != null) {
				mListener.onImageSwitch(mCurrentImage);
			}
			beginScroll(getScrollX(), 0, disX, 0, SCROLL_NEXT);
		}
	}

	/**
	 * 滾動到上一張圖片。
	 */
	public void scrollToPrevious() {
		if (mScroller.isFinished()) {
			int disX = -mImageWidth - getScrollX();
			checkImageSwitchBorder(SCROLL_PREVIOUS);
			if (mListener != null) {
				mListener.onImageSwitch(mCurrentImage);
			}
			beginScroll(getScrollX(), 0, disX, 0, SCROLL_PREVIOUS);
		}
	}

	/**
	 * 滾動回原圖片。
	 */
	public void scrollBack() {
		if (mScroller.isFinished()) {
			beginScroll(getScrollX(), 0, -getScrollX(), 0, SCROLL_BACK);
		}
	}

	/**
	 * 回收所有圖片物件,釋放記憶體。
	 */
	public void clear() {
		for (int i = 0; i < mCount; i++) {
			Image3DView childView = (Image3DView) getChildAt(i);
			childView.recycleBitmap();
		}
	}

	/**
	 * 讓控制元件中的所有圖片開始滾動。
	 */
	private void beginScroll(int startX, int startY, int dx, int dy,
			final int action) {
		int duration = (int) (700f / mImageWidth * Math.abs(dx));
		mScroller.startScroll(startX, startY, dx, dy, duration);
		invalidate();
		handler.postDelayed(new Runnable() {
			@Override
			public void run() {
				if (action == SCROLL_NEXT || action == SCROLL_PREVIOUS) {
					forceToRelayout = true;
					requestLayout();
				}
			}
		}, duration);
	}

	/**
	 * 根據當前圖片的下標和傳入的item引數,來判斷item位置上應該顯示哪張圖片。
	 * 
	 * @param item
	 *            取值範圍是1-5
	 * @return 對應item位置上應該顯示哪張圖片。
	 */
	private int getIndexForItem(int item) {
		int index = -1;
		index = mCurrentImage + item - 3;
		while (index < 0) {
			index = index + mCount;
		}
		while (index > mCount - 1) {
			index = index - mCount;
		}
		return index;
	}

	/**
	 * 重新整理所有圖片的顯示狀態,包括當前的旋轉角度。
	 */
	private void refreshImageShowing() {
		for (int i = 0; i < mItems.length; i++) {
			Image3DView childView = (Image3DView) getChildAt(mItems[i]);
			childView.setRotateData(i, getScrollX());
			childView.invalidate();
		}
	}

	/**
	 * 檢查圖片的邊界,防止圖片的下標超出規定範圍。
	 */
	private void checkImageSwitchBorder(int action) {
		if (action == SCROLL_NEXT && ++mCurrentImage >= mCount) {
			mCurrentImage = 0;
		} else if (action == SCROLL_PREVIOUS && --mCurrentImage < 0) {
			mCurrentImage = mCount - 1;
		}
	}

	/**
	 * 判斷是否應該滾動到下一張圖片。
	 */
	private boolean shouldScrollToNext(int velocityX) {
		return velocityX < -SNAP_VELOCITY || getScrollX() > mImageWidth / 2;
	}

	/**
	 * 判斷是否應該滾動到上一張圖片。
	 */
	private boolean shouldScrollToPrevious(int velocityX) {
		return velocityX > SNAP_VELOCITY || getScrollX() < -mImageWidth / 2;
	}

	/**
	 * 圖片滾動的監聽器
	 */
	public interface OnImageSwitchListener {

		/**
		 * 當圖片滾動時會回撥此方法
		 * 
		 * @param currentImage
		 *            當前圖片的座標
		 */
		void onImageSwitch(int currentImage);

	}
}

這段程式碼也比較長,我們來一點點進行分析。在onLayout()方法首先要判斷子檢視個數是不是大於等於5,如果不足5個則圖片輪播器無法正常顯示,直接return掉。如果大於等於5個,就會通過一個for迴圈來為每個子檢視分配顯示的位置,而每個子檢視都是一個Image3DView,在for迴圈中又會呼叫Image3DView的initImageViewBitmap()方法來為每個控制元件執行初始化操作,之後會呼叫refreshImageShowing()方法來重新整理圖片的顯示狀態。

接著當手指在Image3DSwitchView控制元件上滑動的時候就會進入到onTouchEvent()方法中,當手指按下時會記錄按下時的橫座標,然後當手指滑動時會計算出滑動的距離,並呼叫scrollBy()方法來進行滾動,當手指離開螢幕時會距離當前滑動的距離和速度來決定,是滾動到下一張圖片,還是滾動到上一張圖片,還是滾動回原圖片。分別呼叫的方法是scrollToNext()、scrollToPrevious()和scrollBack()。

在scrollToNext()方法中會先計算一下還需滾動的距離,然後進行一下邊界檢查,防止當前圖片的下標超出合理範圍,接著會呼叫beginScroll()方法來進行滾動。在beginScroll()方法中其實就是呼叫了Scroller的startScroll()方法來執行滾動操作的,當滾動結束後還會呼叫requestLayout()方法來要求重新佈局,之後onLayout()方法就會重新執行,每個圖片的位置也就會跟著改變了。至於scrollToPrevious()和scrollBack()方法的原理也是一樣的,這裡就不再重複分析了。

那麼在onLayout()方法的最後呼叫的refreshImageShowing()方法到底執行了什麼操作呢?其實就是遍歷了一下每個Image3DView控制元件,然後呼叫它的setRotateData()方法,並把圖片的下標和滾動距離傳進去,這樣每張圖片就知道應該如何進行旋轉了。

另外一些其它的細節就不在這裡講解了,註釋寫的還是比較詳細的,你可以慢慢地去分析和理解。

那麼下面我們來看下如何使用Image3DSwitchView這個控制元件吧,開啟或新建activity_main.xml作為程式的主佈局檔案,程式碼如下所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fff" >

    <com.example.imageswitchviewtest.Image3DSwitchView
        android:id="@+id/image_switch_view"
        android:layout_width="match_parent"
        android:layout_height="150dp" >

        <com.example.imageswitchviewtest.Image3DView
            android:id="@+id/image1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/image1" />

        <com.example.imageswitchviewtest.Image3DView
            android:id="@+id/image2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/image2" />

        <com.example.imageswitchviewtest.Image3DView
            android:id="@+id/image3"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/image3" />

        <com.example.imageswitchviewtest.Image3DView
            android:id="@+id/image4"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/image4" />

        <com.example.imageswitchviewtest.Image3DView
            android:id="@+id/image5"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/image5" />

        <com.example.imageswitchviewtest.Image3DView
            android:id="@+id/image6"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/image6" />

        <com.example.imageswitchviewtest.Image3DView
            android:id="@+id/image7"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/image7" />
    </com.example.imageswitchviewtest.Image3DSwitchView>

</RelativeLayout>

可以看到,這裡我們引入了一個Image3DSwitchView控制元件,然後在這個控制元件下面又添加了7個Image3DView控制元件,每個Image3DView其實就是一個ImageView,因此我們可以通過android:src屬於給它指定一張圖片。注意前面也說過了,Image3DSwitchView控制元件下的子控制元件必須大於等於5個,不然將無法正常顯示。

程式碼到這裡就寫得差不多了,現在執行一下程式就可以看到一個3D版的圖片輪播器,使用手指進行滑動可以檢視更多的圖片,如下圖所示:


怎麼樣?效果還是非常不錯的吧!除此之外,Image3DSwitchView中還提供了setCurrentImage()方法和setOnImageSwitchListener()方法,分別可用於設定當前顯示哪張圖片,以及設定圖片滾動的監聽器,有了這些方法,你可以更加輕鬆地在Image3DSwitchView的基礎上進行擴充套件,比如說加入頁籤顯示功能等。

好了,今天的講解就到這裡,有疑問的朋友可以在下面留言(不過最近工作著實繁忙,恐怕無法一一回復大家)。

關注我的技術公眾號,每天都有優質技術文章推送。關注我的娛樂公眾號,工作、學習累了的時候放鬆一下自己。

微信掃一掃下方二維碼即可關注:

        

相關推薦

Android高階圖片滾動控制元件編寫3D版的圖片

大家好,好久不見了,最近由於工作特別繁忙,已經有一個多月的時間沒寫部落格了,我也是深感慚愧。那麼今天的這篇既然是闊別了一個多月的文章,當然要帶來更加給力點的內容了,那麼話不多說,趕快進入到今天的正題吧。說到圖片輪播器,很多的Android應用中都會帶有這個功能,比如說網易新聞

Android高階圖片滾動控制元件編寫3D版的圖片 一屏顯示多個圖片

大家好,好久不見了,最近由於工作特別繁忙,已經有一個多月的時間沒寫部落格了,我也是深感慚愧。那麼今天的這篇既然是闊別了一個多月的文章,當然要帶來更加給力點的內容了,那麼話不多說,趕快進入到今天的正題吧。 說到圖片輪播器,很多的Android應用中都會帶有這個

Android 層疊卡片控制元件仿"探探app"

StackLayout 簡介:Android 層疊卡片控制元件,防"探探 app" 1.支援自定義卡片的堆疊效果 2.支援自定義卡片移除動畫 3.支援載入更多 更多:作者   提 Bug    標籤: android-stack-card-swipe- 功能

Android 自定義RatingBar控制元件顯示不全問題

最近專案要用到 自定義RatingBar控制元件 但是自定義好了樣式發現,星星只顯示一半,於是在網上找各種解決方法。 最後竟然是直接把資源圖片,移動到較高解析度的資料夾裡面。 我的解決做法如下,如果有更好的方法,請留言告知。 一,先寫一個drawable,設定好backg

Android有沒有什麼控制元件支援選擇一段時間

不要日期,只要整點之間的時間段選擇, 例如選:08:00 - 10:00、 13:00 - 17:00等。 請問有沒有這種元件,或者其他work around思路也行。 下面是我的一些搜尋:     1. android 自帶的TimePicker, 只能選擇單個時間點,不

Android開發之AutoCompleteTextView控制元件自動提示

AutoCompleteTextView是一個提供了聯想詞的控制元件,可以看做是EditText的升級版本 佈局: <AutoCompleteTextView android:

Android banner圖自動滾動控制元件

簡介 現在的絕大數app都有banner介面,實現迴圈播放多個廣告圖片和手動滑動迴圈等功能。因為ViewPager並不支援迴圈翻頁, 所以要實現迴圈還得需要自己去動手,我就把專案中的控制元件剔了出來,希望大家覺得有用。目前框架可以進行不同樣式、不同動畫設定,

Android仿微信朋友圈九宮格圖片展示自定義控制元件支援縮放動畫~

一直對微信朋友圈九宮格圖片顯示控制元件比較好奇,找到一篇介紹相關騷操作的部落格 部落格雖好但是不夠完美,缺少點選圖片預覽頁面和縮放動畫,作為一個不斷追求完美主義的人,我想把這個控制元件結合到專案中而不是單純作為一個控制元件。 下面是我的實現效果圖: (

android studio引入一個自定義的佈局自定義控制元件避免每一個活動中都編寫一樣佈局程式碼的問題

本次演示的是標題欄上建立按鈕,即 引入自定義佈局和自定義控制元件的應用十分的廣泛,它的形成的效果很多的應用程式都有,我們可以自定義標題欄,因為普通的標題欄就是一行文字,但是,我們可以發現,很多手機軟體的標題欄上都有返回,或者 進入的按鈕,尤其是全面屏的手機。而且它還能解

Android自定義View高仿QQ音樂歌詞滾動控制元件

最近在以QQ音樂為樣板做一個手機音樂播放器,原始碼下篇博文放出。今天我想聊的是這個QQ音樂播放器中歌詞顯示控制元件的問題,和小夥伴們一起來探討怎麼實現這個歌詞滾動的效果。OK,廢話不多說,先來看看效果圖:好,接下來我們就來看看怎麼實現這樣一個效果。本文主要包括如下幾方面內容:

Android Studio中ListView控制元件圖片+文字文字顯示不出來

  今天在學習 ListView 控制元件的時候,想做一個滾動介面,顯示圖片+文字。 想象的是這樣子的: 沒想到執行的時候是這樣子的: 於是我抱著試一試的心態壓縮了一下圖片:結果就按照想的顯示出來了。 我覺得應該還有其他的解決辦法,歡迎指出。

Android中使用ImageView控制元件顯示網路圖片

在android4.0以後的版本中,為了使得主介面流暢,所以設定了不允許在主執行緒中訪問網路,為了安全,又不允許在其它執行緒中訪問控制元件,這樣就造成了ImageView等需要使用網路的控制元件更新時的問題,本文以Handler+Runnable的方式實現了ImageView控制元件顯示網路圖片.

android 禁止scrollview 因控制元件變化自動滾動到底的方法

網上的說法是焦點問題導致,新焦點在下面時,檢視會滾動使焦點可見。但網上提的在scrollview裡層的LinearLayout裡新增    android:focusable="true" android:focusableIn

iOS開發圖片加標題滾動控制元件封裝

說明 · 使用UICollectionView實現,封裝在 HorizontalSlipMenuView 中,使用時只要例項化 HorizontalSlipMenuView 並設定資料來源後,新增到父檢視上即可 Demo地址:https://github.com/liujunwei

Android常用控制元件用執行緒寫一個進度條。

一、事件監聽(三種寫法) 1、標籤上直接繫結監聽方法       public void xxx(View view) 2、 建立監聽器物件,元件再繫結監聽器物件 2.1、匿名內部類 2.2、使用匿名內部類並定義成全域性的屬性 二、文字框(TextView) 1、T

Android Studio的基本控制元件 圖片框與進度條

今日重點: 1. Toast(吐絲框); 2. ImageView; 3. ProgressBar (進度條); 4. 如何讓子執行緒成功連線主執行緒(重點)。 技術分點 Toast(吐絲框); 1.1 Toast是Android

android自定義星級評分控制元件可實現只顯示實心星星

話不多說,上圖 近日app需求弄一個等級展示,看了下UI圖,只顯示實星(點亮的星星).如圖 但是網上關於星級評分的例子大多這樣 也展示虛心星星 通過自定義View package com.starsbar; import android.content.C

Android仿百度高德地圖位置交換控制元件水平or垂直交換控制元件位置

  開始看到這個需求,準備使用檢視動畫 TranslateAnimation 來實現。但是把因為檢視動畫只是移動了檢視,控制元件的位置沒有改變,導致只能執行一次交換的動作,不能交換回來。逐放棄改用屬性動畫來實現。直接交換兩個控制元件的位置,交換後在將原來控制元件的值賦予交換後

一個Android文字控制元件實現了可垂直跑、可水平跑的跑馬燈

Android文字輪播控制元件 現在的絕大數APP特別是類似淘寶京東等這些大型APP都有文字輪播介面,實現迴圈輪播多個廣告詞等功能;這種控制元件俗稱“跑馬燈”,而TextBannerView已經實現了可垂直跑、可水平跑的跑馬燈了。 效果圖 Attribute

Android開發技巧——定製仿微信圖片裁剪控制元件

拍照——裁剪,或者是選擇圖片——裁剪,是我們設定頭像或上傳圖片時經常需要的一組操作。上篇講了Camera的使用,這篇講一下我對圖片裁剪的實現。 背景 下面的需求都來自產品。 裁剪圖片要像微信那樣,拖動和放大的是圖片,裁剪框不動。 裁剪框外的內容要有半透