大家好,好久不見了,最近由于工作特別繁忙,已經有一個多月的時間沒寫博客了,我也是深感慚愧。那么今天的這篇既然是闊別了一個多月的文章,當然要帶來更加給力點的內容了,那么話不多說,趕快進入到今天的正題吧。
說到圖片輪播器,很多的Android應用中都會帶有這個功能,比如說網易新聞、淘寶等。最新我們公司的一款應用也加入了這個功能,并且在圖片輪播的基礎上還增加了三維立體的效果,但比較遺憾的是,整體效果并不理想,用戶體驗性比較糟糕。因此,我就花了點時間去編寫了一個效果更好的3D圖片輪播器,自我感覺還是比較滿意的,這里果斷寫一篇博客來分享給大家。
首先來介紹一下實現原理吧,傳統的圖片輪播器在一個界面上只會顯示一張圖片,要用手指進行左右滑動才能看到其它的圖片。這里我們將思維發散一下,允許在一個界面上同時顯示三張圖片,再通過Camera的方式對左右的兩張圖進行3D旋轉,這樣就能制作出一種立體的圖片輪播器了,原理示意圖如下所示:
對圖片進行立體操作還是要使用到Camera技術,如果你對這個技術還不太熟悉,可以到網上搜一些相關資料,或者參考我前面的一篇文章:Android實現中軸旋轉特效 Android制作別樣的圖片瀏覽器 。
那么我們現在就開始動手吧,首先新建一個Android項目,起名叫做ImageSwitchViewTest。
然后新建一個Image3DView繼承自ImageView,它會繼承ImageView的所有屬性,并且加入3D旋轉的功能,代碼如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
|
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,代碼如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
|
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作為程序的主布局文件,代碼如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
< 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的基礎上進行擴展,比如說加入頁簽顯示功能等。
好了,今天的講解就到這里,有疑問的朋友可以在下面留言(不過最近工作著實繁忙,恐怕無法一一回復大家)。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/guolin_blog/article/details/17482089