Android客户端中,我们经常要实现图片滚轮的效果。
实现的方式就是自定义相关View,这里主要是包括两个类:ImageScroller和PagerIndicator。
PagerIndicator类:
public class PagerIndicator extends ViewGroup { public static int mMaxTotalItems = 9; private int mTotalItems; private int mCurrentItem; private int mDotDrawableId;//滑动的图片 public PagerIndicator(Context context, AttributeSet attrs) { super(context, attrs); initPager(); } public PagerIndicator(Context context) { super(context); initPager(); } private void initPager(){ setFocusable(false); setWillNotDraw(false); mDotDrawableId=R.drawable.pager_dots; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if(mTotalItems<=0) return; createLayout(); } private void updateLayout(){ for(int i=0;i<getChildCount();i++){ final ImageView img=(ImageView) getChildAt(i); TransitionDrawable tmp=(TransitionDrawable)img.getDrawable(); if(i==mCurrentItem){ tmp.startTransition(0); }else{ tmp.resetTransition(); } } } private void createLayout(){ detachAllViewsFromParent(); int dotWidth=getResources().getDrawable(mDotDrawableId).getIntrinsicWidth(); int separation=9; int marginLeft=((getWidth())/2)-(((mTotalItems*dotWidth)/2)+(((mTotalItems-1)*separation)/2)); int marginTop=((getHeight())/2)-(dotWidth/2); for(int i=0;i<mTotalItems;i++){ ImageView dot=new ImageView(getContext()); TransitionDrawable td=(TransitionDrawable)getResources().getDrawable(mDotDrawableId); td.setCrossFadeEnabled(true); dot.setImageDrawable(td); ViewGroup.LayoutParams p; p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT); dot.setLayoutParams(p); int childHeightSpec = getChildMeasureSpec( MeasureSpec.makeMeasureSpec(dotWidth, MeasureSpec.UNSPECIFIED), 0, p.height); int childWidthSpec = getChildMeasureSpec( MeasureSpec.makeMeasureSpec(dotWidth, MeasureSpec.EXACTLY), 0, p.width); dot.measure(childWidthSpec, childHeightSpec); int left=marginLeft+(i*(dotWidth+separation)); dot.layout(left, marginTop, left+dotWidth,marginTop+dotWidth ); addViewInLayout(dot, getChildCount(), p, true); if(i==mCurrentItem){ TransitionDrawable tmp=(TransitionDrawable)dot.getDrawable(); tmp.startTransition(0); } } postInvalidate(); } public int getTotalItems() { return mTotalItems; } public void setTotalItems(int totalItems) { if(totalItems!=mTotalItems){ if(totalItems <= mMaxTotalItems){ mTotalItems = totalItems; } else { mTotalItems = mMaxTotalItems; } createLayout(); } } public int getCurrentItem() { return mCurrentItem; } public void setCurrentItem(int currentItem) { if(currentItem!=mCurrentItem){ mCurrentItem = currentItem; updateLayout(); } } /** * 向左翻页 * @param pageLeave 在最左边还有多少页没显示 * @return 左边超出的页数 */ public void rollLeft(int currentPage, int totalChild){ if(mCurrentItem == 0){ setCurrentItem(Math.min(currentPage, mTotalItems / 2) - 1); } else { setCurrentItem(Math.max(0, mCurrentItem - 1)); } } /** * 向右翻页 * @param pageLeave 在最右边还有多少页没显示 * @return 右边超出的页数 */ public void rollRight(int currentPage, int totalChild){ if(mCurrentItem == (mTotalItems - 1)){ final int pageLeave = totalChild - currentPage - 1; if(pageLeave < mTotalItems / 2){ setCurrentItem(mTotalItems - pageLeave); } else { setCurrentItem(mTotalItems / 2); } } else { setCurrentItem(Math.min((mCurrentItem + 1), mTotalItems - 1)); } } }
ImageScroller类:
public class ImageScroller extends ViewGroup { private static String TAG = ImageScroller.class.getSimpleName(); private static final int INVALID_SCREEN = -1; private static final int SNAP_VELOCITY = 800; private final static int TOUCH_STATE_REST = 0; private final static int TOUCH_STATE_SCROLLING = 1; private boolean mTapChange; private PagerIndicator mIndicator; private int currentScreen; private int nextScreen = INVALID_SCREEN; private int lastMotionX; private int lastMotionY; private int touchState = TOUCH_STATE_REST; private int touchSlop; private Map<String, SoftReference<Bitmap>> mCacheBitmap; private Set<ImageView> mMissingView; private ExecutorService mExecutorService; private VelocityTracker mVelocityTracker; private Scroller scroller; private Resources mResources; private static final int FETCH_IMAGE_BITMAP = 1; private boolean mSyncLoad = false; //是否需异步加载 public ImageScroller(Context context) { this(context, null); } public ImageScroller(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ImageScroller(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); touchSlop = ViewConfiguration.getTouchSlop();//用户拖动时应该的距离 scroller = new Scroller(getContext()); mResources = getResources(); mCacheBitmap = new HashMap<String, SoftReference<Bitmap>>(); mMissingView = new HashSet<ImageView>(); mExecutorService = Executors.newFixedThreadPool(3);//线程池 } public void setPageIndicator(PagerIndicator indicator){ mIndicator = indicator; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); } } @Override protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) { int childLeft = 0; final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != View.GONE) { final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight()); childLeft += childWidth; } } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && (touchState != TOUCH_STATE_REST)) { return true; } final float x = ev.getX(); final float y = ev.getY(); switch (action) { case MotionEvent.ACTION_MOVE: final int xDiff = (int) Math.abs(x - lastMotionX); final int yDiff = (int) Math.abs(y - lastMotionY); boolean xMoved = xDiff > touchSlop; boolean yMoved = yDiff > touchSlop; if(xMoved || yMoved){ if (xMoved) { touchState = TOUCH_STATE_SCROLLING; } } break; case MotionEvent.ACTION_DOWN: lastMotionX = (int) x; touchState = scroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING; break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: touchState = TOUCH_STATE_REST; break; } return touchState != TOUCH_STATE_REST; } @Override public boolean onTouchEvent(MotionEvent ev) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); final int action = ev.getAction(); final float x = ev.getX(); switch (action) { case MotionEvent.ACTION_DOWN: if (!scroller.isFinished()) { scroller.abortAnimation(); } lastMotionX = (int) x; break; case MotionEvent.ACTION_MOVE: if (touchState == TOUCH_STATE_SCROLLING) { final int deltaX = (int) (lastMotionX - x); lastMotionX = (int) x; if (deltaX < 0 && getScrollX() > 0) { scrollBy(Math.max(-getScrollX(), deltaX), 0); } else if (deltaX > 0) { final int availableToScroll = getChildAt(getChildCount() - 1).getRight() - getScrollX() - getWidth(); if (availableToScroll > 0) { scrollBy(Math.min(availableToScroll, deltaX), 0); } } } break; case MotionEvent.ACTION_UP: if (touchState == TOUCH_STATE_SCROLLING) { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000); int velocityX = (int) velocityTracker.getXVelocity(); if (velocityX > SNAP_VELOCITY && currentScreen > 0) { scrollToScreen(currentScreen - 1); } else if (velocityX < -SNAP_VELOCITY && currentScreen < getChildCount() - 1) { scrollToScreen(currentScreen + 1); } else { snapToDestination(); } if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } } touchState = TOUCH_STATE_REST; break; case MotionEvent.ACTION_CANCEL: touchState = TOUCH_STATE_REST; break; } return true; } private void snapToDestination() { final int screenWidth = getWidth(); final int whichScreen = (getScrollX() + (screenWidth / 2)) / screenWidth; scrollToScreen(whichScreen); } public void scrollToScreen(int whichScreen) { boolean changingScreens = whichScreen != currentScreen; nextScreen = whichScreen; View focusedChild = getFocusedChild(); if (focusedChild != null && changingScreens && focusedChild == getChildAt(currentScreen)) { focusedChild.clearFocus(); } final int newX = whichScreen * getWidth(); final int delta = newX - getScrollX(); scroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2); invalidate(); } public void scrollToScreen(int whichScreen, int duration) { boolean changingScreens = whichScreen != currentScreen; nextScreen = whichScreen; View focusedChild = getFocusedChild(); if (focusedChild != null && changingScreens && focusedChild == getChildAt(currentScreen)) { focusedChild.clearFocus(); } final int newX = whichScreen * getWidth(); final int delta = newX - getScrollX(); scroller.startScroll(getScrollX(), 0, delta, 0, duration); invalidate(); } @Override public void computeScroll() { if (scroller.computeScrollOffset()) { scrollTo(scroller.getCurrX(), scroller.getCurrY()); postInvalidate(); } else if (nextScreen != INVALID_SCREEN) { if(mTapChange){ mTapChange = false; } else { if(mIndicator != null && currentScreen < nextScreen){ mIndicator.rollRight(currentScreen, getChildCount()); } else if (mIndicator != null && currentScreen > nextScreen){ mIndicator.rollLeft(currentScreen, getChildCount()); } } currentScreen = nextScreen; nextScreen = INVALID_SCREEN; if(mSyncLoad){ ImageView child = (ImageView) getChildAt(currentScreen); if(child == null){ return; } Movie movie = (Movie) child.getTag(); if(movie != null){ SoftReference<Bitmap> ref = mCacheBitmap.get(movie.getImage().getBigImage()); if(ref != null){ Bitmap bitmap = ref.get(); if(bitmap != null) { child.setImageBitmap(bitmap); } } else { fetchBitmapFromCache(child, movie); } } } } } private void fetchBitmapFromCache(final ImageView child, final Movie coupon) { mMissingView.add(child); mExecutorService.execute(new Runnable() { @Override public void run() { String imagePath = coupon.getImage().getBigImage(); Bitmap bitmap = loadBitmapFromUri(imagePath); if(bitmap != null){ bitmap = ImageUtil.getRoundedCornerBitmap(bitmap, 10); mCacheBitmap.put(imagePath, new SoftReference<Bitmap>(bitmap)); Message message = new Message(); message.what = FETCH_IMAGE_BITMAP; message.obj = child; handler.sendMessage(message); } } }); } private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch(msg.what){ case FETCH_IMAGE_BITMAP: ImageView view = (ImageView) msg.obj; Movie movie = (Movie) view.getTag(); if(view == null || movie == null){ break; } SoftReference<Bitmap> ref = mCacheBitmap.get(movie.getImage().getBigImage()); if(ref != null){ Bitmap bitmap = ref.get(); if(bitmap != null){ view.setImageBitmap(bitmap); mMissingView.remove(view); } } else { mCacheBitmap.remove(movie.getId()); } break; } } }; private Bitmap loadBitmapFromUri(String path){ Bitmap bitmap = null; URL url; InputStream i = null; try { url = new URL(path); i = (InputStream) url.getContent(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } bitmap = BitmapFactory.decodeStream(i); return bitmap; } public void setTapChange(boolean value){ mTapChange = value; } public void setSyncLoad(boolean syncLoad) { mSyncLoad = syncLoad; } }