Guest User

DynamListView.java

a guest
Apr 9th, 2015
304
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 20.51 KB | None | 0 0
  1.  
  2. public class DynamicListView extends ListView {
  3.  
  4.     final static String TAG = DynamicListView.class.getSimpleName();
  5.  
  6.     private final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 15;
  7.     private final int MOVE_DURATION = 150;
  8.     private final int LINE_THICKNESS = 15;
  9.  
  10.     public ArrayList<String> mCheeseList;
  11.  
  12.     private int mLastEventY = -1;
  13.  
  14.     private int mDownY = -1;
  15.     private int mDownX = -1;
  16.  
  17.     private int mTotalOffset = 0;
  18.  
  19.     private boolean mCellIsMobile = false;
  20.     private boolean mIsMobileScrolling = false;
  21.     private int mSmoothScrollAmountAtEdge = 0;
  22.  
  23.     private final int INVALID_ID = -1;
  24.     private long mAboveItemId = INVALID_ID;
  25.     private long mMobileItemId = INVALID_ID;
  26.     private long mBelowItemId = INVALID_ID;
  27.  
  28.     private BitmapDrawable mHoverCell;
  29.     private Rect mHoverCellCurrentBounds;
  30.     private Rect mHoverCellOriginalBounds;
  31.  
  32.     private final int INVALID_POINTER_ID = -1;
  33.     private int mActivePointerId = INVALID_POINTER_ID;
  34.  
  35.     private boolean mIsWaitingForScrollFinish = false;
  36.     private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
  37.  
  38.     public DynamicListView(Context context) {
  39.         super(context);
  40.         init(context);
  41.     }
  42.  
  43.     public DynamicListView(Context context, AttributeSet attrs, int defStyle) {
  44.         super(context, attrs, defStyle);
  45.         init(context);
  46.     }
  47.  
  48.     public DynamicListView(Context context, AttributeSet attrs) {
  49.         super(context, attrs);
  50.         init(context);
  51.     }
  52.  
  53.     public void init(Context context) {
  54.         setOnItemLongClickListener(mOnItemLongClickListener);
  55.         setOnScrollListener(mScrollListener);
  56.         DisplayMetrics metrics = context.getResources().getDisplayMetrics();
  57.         mSmoothScrollAmountAtEdge = (int)(SMOOTH_SCROLL_AMOUNT_AT_EDGE / metrics.density);
  58.     }
  59.  
  60.     /**
  61.      * Listens for long clicks on any mItems in the listview. When a cell has
  62.      * been selected, the hover cell is created and set up.
  63.      */
  64.     private OnItemLongClickListener mOnItemLongClickListener =
  65.             new OnItemLongClickListener() {
  66.                 public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int pos, long id) {
  67.                     mTotalOffset = 0;
  68.  
  69.                     int position = pointToPosition(mDownX, mDownY);
  70.                     int itemNum = position - getFirstVisiblePosition();
  71.  
  72.                     View selectedView = getChildAt(itemNum);
  73.                     mMobileItemId = getAdapter().getItemId(position);
  74.                     mHoverCell = getAndAddHoverView(selectedView);
  75.                     selectedView.setVisibility(INVISIBLE);
  76.  
  77.                     mCellIsMobile = true;
  78.  
  79.                     updateNeighborViewsForID(mMobileItemId);
  80.  
  81.                     return true;
  82.                 }
  83.             };
  84.  
  85.     /**
  86.      * Creates the hover cell with the appropriate bitmap and of appropriate
  87.      * size. The hover cell's BitmapDrawable is drawn on top of the bitmap every
  88.      * single time an invalidate call is made.
  89.      */
  90.     private BitmapDrawable getAndAddHoverView(View v) {
  91.  
  92.         int w = v.getWidth();
  93.         int h = v.getHeight();
  94.         int top = v.getTop();
  95.         int left = v.getLeft();
  96.  
  97.         Bitmap b = getBitmapWithBorder(v);
  98.  
  99.         BitmapDrawable drawable = new BitmapDrawable(getResources(), b);
  100.  
  101.         mHoverCellOriginalBounds = new Rect(left, top, left + w, top + h);
  102.         mHoverCellCurrentBounds = new Rect(mHoverCellOriginalBounds);
  103.  
  104.         drawable.setBounds(mHoverCellCurrentBounds);
  105.  
  106.         return drawable;
  107.     }
  108.  
  109.     /** Draws a black border over the screenshot of the view passed in. */
  110.     private Bitmap getBitmapWithBorder(View v) {
  111.         Bitmap bitmap = getBitmapFromView(v);
  112.         Canvas can = new Canvas(bitmap);
  113.  
  114.         Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
  115.  
  116.         Paint paint = new Paint();
  117.         paint.setStyle(Paint.Style.STROKE);
  118.         paint.setStrokeWidth(LINE_THICKNESS);
  119.         paint.setColor(Color.BLACK);
  120.  
  121.         can.drawBitmap(bitmap, 0, 0, null);
  122.         can.drawRect(rect, paint);
  123.  
  124.         return bitmap;
  125.     }
  126.  
  127.     /** Returns a bitmap showing a screenshot of the view passed in. */
  128.     private Bitmap getBitmapFromView(View v) {
  129.         Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
  130.         Canvas canvas = new Canvas (bitmap);
  131.         v.draw(canvas);
  132.         return bitmap;
  133.     }
  134.  
  135.     /**
  136.      * Stores a reference to the views above and below the item currently
  137.      * corresponding to the hover cell. It is important to note that if this
  138.      * item is either at the top or bottom of the list, mAboveItemId or mBelowItemId
  139.      * may be invalid.
  140.      */
  141.     private void updateNeighborViewsForID(long itemID) {
  142.         int position = getPositionForID(itemID);
  143.         ItemsArrayAdapter adapter = ((ItemsArrayAdapter)getAdapter());
  144.         mAboveItemId = adapter.getItemId(position - 1);
  145.         mBelowItemId = adapter.getItemId(position + 1);
  146.     }
  147.  
  148.     /** Retrieves the view in the list corresponding to itemID */
  149.     public View getViewForID (long itemID) {
  150.         int firstVisiblePosition = getFirstVisiblePosition();
  151.         ItemsArrayAdapter adapter = ((ItemsArrayAdapter)getAdapter());
  152.         for(int i = 0; i < getChildCount(); i++) {
  153.             View v = getChildAt(i);
  154.             int position = firstVisiblePosition + i;
  155.             long id = adapter.getItemId(position);
  156.             if (id == itemID) {
  157.                 return v;
  158.             }
  159.         }
  160.         return null;
  161.     }
  162.  
  163.     /** Retrieves the position in the list corresponding to itemID */
  164.     public int getPositionForID (long itemID) {
  165.         View v = getViewForID(itemID);
  166.         if (v == null) {
  167.             return -1;
  168.         } else {
  169.             return getPositionForView(v);
  170.         }
  171.     }
  172.  
  173.     /**
  174.      *  dispatchDraw gets invoked when all the child views are about to be drawn.
  175.      *  By overriding this method, the hover cell (BitmapDrawable) can be drawn
  176.      *  over the listview's mItems whenever the listview is redrawn.
  177.      */
  178.     @Override
  179.     protected void dispatchDraw(Canvas canvas) {
  180.         super.dispatchDraw(canvas);
  181.         if (mHoverCell != null) {
  182.             mHoverCell.draw(canvas);
  183.         }
  184.     }
  185.  
  186.     @Override
  187.     public boolean onTouchEvent (MotionEvent event) {
  188.  
  189.         switch (event.getAction() & MotionEvent.ACTION_MASK) {
  190.             case MotionEvent.ACTION_DOWN:
  191.                 mDownX = (int)event.getX();
  192.                 mDownY = (int)event.getY();
  193.                 mActivePointerId = event.getPointerId(0);
  194.                 break;
  195.             case MotionEvent.ACTION_MOVE:
  196.                 if (mActivePointerId == INVALID_POINTER_ID) {
  197.                     break;
  198.                 }
  199.  
  200.                 int pointerIndex = event.findPointerIndex(mActivePointerId);
  201.  
  202.                 mLastEventY = (int) event.getY(pointerIndex);
  203.                 int deltaY = mLastEventY - mDownY;
  204.  
  205.                 if (mCellIsMobile) {
  206.                     mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left,
  207.                             mHoverCellOriginalBounds.top + deltaY + mTotalOffset);
  208.                     mHoverCell.setBounds(mHoverCellCurrentBounds);
  209.                     invalidate();
  210.  
  211.                     handleCellSwitch();
  212.  
  213.                     mIsMobileScrolling = false;
  214.                     handleMobileCellScroll();
  215.  
  216.                     return false;
  217.                 }
  218.                 break;
  219.             case MotionEvent.ACTION_UP:
  220.                 touchEventsEnded();
  221.                 break;
  222.             case MotionEvent.ACTION_CANCEL:
  223.                 touchEventsCancelled();
  224.                 break;
  225.             case MotionEvent.ACTION_POINTER_UP:
  226.                 /* If a multitouch event took place and the original touch dictating
  227.                  * the movement of the hover cell has ended, then the dragging event
  228.                  * ends and the hover cell is animated to its corresponding position
  229.                  * in the listview. */
  230.                 pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
  231.                         MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  232.                 final int pointerId = event.getPointerId(pointerIndex);
  233.                 if (pointerId == mActivePointerId) {
  234.                     touchEventsEnded();
  235.                 }
  236.                 break;
  237.             default:
  238.                 break;
  239.         }
  240.  
  241.         return super.onTouchEvent(event);
  242.     }
  243.  
  244.     /**
  245.      * This method determines whether the hover cell has been shifted far enough
  246.      * to invoke a cell swap. If so, then the respective cell swap candidate is
  247.      * determined and the data set is changed. Upon posting a notification of the
  248.      * data set change, a layout is invoked to place the cells in the right place.
  249.      * Using a ViewTreeObserver and a corresponding OnPreDrawListener, we can
  250.      * offset the cell being swapped to where it previously was and then animate it to
  251.      * its new position.
  252.      */
  253.     private void handleCellSwitch() {
  254.         final int deltaY = mLastEventY - mDownY;
  255.         int deltaYTotal = mHoverCellOriginalBounds.top + mTotalOffset + deltaY;
  256.  
  257.         View belowView = getViewForID(mBelowItemId);
  258.         View mobileView = getViewForID(mMobileItemId);
  259.         View aboveView = getViewForID(mAboveItemId);
  260.  
  261.         boolean isBelow = (belowView != null) && (deltaYTotal > belowView.getTop());
  262.         boolean isAbove = (aboveView != null) && (deltaYTotal < aboveView.getTop());
  263.  
  264.         if (isBelow || isAbove) {
  265.  
  266.             final long switchItemID = isBelow ? mBelowItemId : mAboveItemId;
  267.             View switchView = isBelow ? belowView : aboveView;
  268.             final int originalItem = getPositionForView(mobileView);
  269.  
  270.             if (switchView == null) {
  271.                 updateNeighborViewsForID(mMobileItemId);
  272.                 return;
  273.             }
  274.  
  275.             swapElements(mCheeseList, originalItem, getPositionForView(switchView));
  276.  
  277.             ((BaseAdapter) getAdapter()).notifyDataSetChanged();
  278.  
  279.             mDownY = mLastEventY;
  280.  
  281.             final int switchViewStartTop = switchView.getTop();
  282.  
  283.             if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.KITKAT){
  284.                 mobileView.setVisibility(View.VISIBLE);
  285.                 switchView.setVisibility(View.INVISIBLE);
  286.             } else{
  287.                 mobileView.setVisibility(View.INVISIBLE);
  288.                 switchView.setVisibility(View.VISIBLE);
  289.             }
  290.  
  291.             updateNeighborViewsForID(mMobileItemId);
  292.  
  293.             final ViewTreeObserver observer = getViewTreeObserver();
  294.             observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
  295.                 public boolean onPreDraw() {
  296.                     observer.removeOnPreDrawListener(this);
  297.  
  298.                     View switchView = getViewForID(switchItemID);
  299.  
  300.                     mTotalOffset += deltaY;
  301.  
  302.                     Log.v(TAG, "switchview isnull=" + (switchView==null));
  303.                     int switchViewNewTop = switchView.getTop();
  304.                     int delta = switchViewStartTop - switchViewNewTop;
  305.  
  306.                     switchView.setTranslationY(delta);
  307.  
  308.                     ObjectAnimator animator = ObjectAnimator.ofFloat(switchView,
  309.                             View.TRANSLATION_Y, 0);
  310.                     animator.setDuration(MOVE_DURATION);
  311.                     animator.start();
  312.  
  313.                     return true;
  314.                 }
  315.             });
  316.         }
  317.     }
  318.  
  319.     private void swapElements(ArrayList arrayList, int indexOne, int indexTwo) {
  320.         Object temp = arrayList.get(indexOne);
  321.         arrayList.set(indexOne, arrayList.get(indexTwo));
  322.         arrayList.set(indexTwo, temp);
  323.     }
  324.  
  325.  
  326.     /**
  327.      * Resets all the appropriate fields to a default state while also animating
  328.      * the hover cell back to its correct location.
  329.      */
  330.     private void touchEventsEnded () {
  331.         final View mobileView = getViewForID(mMobileItemId);
  332.         if (mCellIsMobile|| mIsWaitingForScrollFinish) {
  333.             mCellIsMobile = false;
  334.             mIsWaitingForScrollFinish = false;
  335.             mIsMobileScrolling = false;
  336.             mActivePointerId = INVALID_POINTER_ID;
  337.  
  338.             // If the autoscroller has not completed scrolling, we need to wait for it to
  339.             // finish in order to determine the final location of where the hover cell
  340.             // should be animated to.
  341.             if (mScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
  342.                 mIsWaitingForScrollFinish = true;
  343.                 return;
  344.             }
  345.  
  346.             mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left, mobileView.getTop());
  347.  
  348.             ObjectAnimator hoverViewAnimator = ObjectAnimator.ofObject(mHoverCell, "bounds",
  349.                     sBoundEvaluator, mHoverCellCurrentBounds);
  350.             hoverViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  351.                 @Override
  352.                 public void onAnimationUpdate(ValueAnimator valueAnimator) {
  353.                     invalidate();
  354.                 }
  355.             });
  356.             hoverViewAnimator.addListener(new AnimatorListenerAdapter() {
  357.                 @Override
  358.                 public void onAnimationStart(Animator animation) {
  359.                     setEnabled(false);
  360.                 }
  361.  
  362.                 @Override
  363.                 public void onAnimationEnd(Animator animation) {
  364.                     mAboveItemId = INVALID_ID;
  365.                     mMobileItemId = INVALID_ID;
  366.                     mBelowItemId = INVALID_ID;
  367.                     mobileView.setVisibility(VISIBLE);
  368.                     mHoverCell = null;
  369.                     setEnabled(true);
  370.                     invalidate();
  371.                 }
  372.             });
  373.             hoverViewAnimator.start();
  374.         } else {
  375.             touchEventsCancelled();
  376.         }
  377.     }
  378.  
  379.     /**
  380.      * Resets all the appropriate fields to a default state.
  381.      */
  382.     private void touchEventsCancelled () {
  383.         View mobileView = getViewForID(mMobileItemId);
  384.         if (mCellIsMobile) {
  385.             mAboveItemId = INVALID_ID;
  386.             mMobileItemId = INVALID_ID;
  387.             mBelowItemId = INVALID_ID;
  388.             mobileView.setVisibility(VISIBLE);
  389.             mHoverCell = null;
  390.             invalidate();
  391.         }
  392.         mCellIsMobile = false;
  393.         mIsMobileScrolling = false;
  394.         mActivePointerId = INVALID_POINTER_ID;
  395.     }
  396.  
  397.     /**
  398.      * This TypeEvaluator is used to animate the BitmapDrawable back to its
  399.      * final location when the user lifts his finger by modifying the
  400.      * BitmapDrawable's bounds.
  401.      */
  402.     private final static TypeEvaluator<Rect> sBoundEvaluator = new TypeEvaluator<Rect>() {
  403.         public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
  404.             return new Rect(interpolate(startValue.left, endValue.left, fraction),
  405.                     interpolate(startValue.top, endValue.top, fraction),
  406.                     interpolate(startValue.right, endValue.right, fraction),
  407.                     interpolate(startValue.bottom, endValue.bottom, fraction));
  408.         }
  409.  
  410.         public int interpolate(int start, int end, float fraction) {
  411.             return (int)(start + fraction * (end - start));
  412.         }
  413.     };
  414.  
  415.     /**
  416.      *  Determines whether this listview is in a scrolling state invoked
  417.      *  by the fact that the hover cell is out of the bounds of the listview;
  418.      */
  419.     private void handleMobileCellScroll() {
  420.         mIsMobileScrolling = handleMobileCellScroll(mHoverCellCurrentBounds);
  421.     }
  422.  
  423.     /**
  424.      * This method is in charge of determining if the hover cell is above
  425.      * or below the bounds of the listview. If so, the listview does an appropriate
  426.      * upward or downward smooth scroll so as to reveal new mItems.
  427.      */
  428.     public boolean handleMobileCellScroll(Rect r) {
  429.         int offset = computeVerticalScrollOffset();
  430.         int height = getHeight();
  431.         int extent = computeVerticalScrollExtent();
  432.         int range = computeVerticalScrollRange();
  433.         int hoverViewTop = r.top;
  434.         int hoverHeight = r.height();
  435.  
  436.         if (hoverViewTop <= 0 && offset > 0) {
  437.             smoothScrollBy(-mSmoothScrollAmountAtEdge, 0);
  438.             return true;
  439.         }
  440.  
  441.         if (hoverViewTop + hoverHeight >= height && (offset + extent) < range) {
  442.             smoothScrollBy(mSmoothScrollAmountAtEdge, 0);
  443.             return true;
  444.         }
  445.  
  446.         return false;
  447.     }
  448.  
  449.     public void setCheeseList(ArrayList<String> cheeseList) {
  450.         mCheeseList = cheeseList;
  451.     }
  452.  
  453.     /**
  454.      * This scroll listener is added to the listview in order to handle cell swapping
  455.      * when the cell is either at the top or bottom edge of the listview. If the hover
  456.      * cell is at either edge of the listview, the listview will begin scrolling. As
  457.      * scrolling takes place, the listview continuously checks if new cells became visible
  458.      * and determines whether they are potential candidates for a cell swap.
  459.      */
  460.     private OnScrollListener mScrollListener = new OnScrollListener () {
  461.  
  462.         private int mPreviousFirstVisibleItem = -1;
  463.         private int mPreviousVisibleItemCount = -1;
  464.         private int mCurrentFirstVisibleItem;
  465.         private int mCurrentVisibleItemCount;
  466.         private int mCurrentScrollState;
  467.  
  468.         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
  469.                              int totalItemCount) {
  470.             mCurrentFirstVisibleItem = firstVisibleItem;
  471.             mCurrentVisibleItemCount = visibleItemCount;
  472.  
  473.             mPreviousFirstVisibleItem = (mPreviousFirstVisibleItem == -1) ? mCurrentFirstVisibleItem
  474.                     : mPreviousFirstVisibleItem;
  475.             mPreviousVisibleItemCount = (mPreviousVisibleItemCount == -1) ? mCurrentVisibleItemCount
  476.                     : mPreviousVisibleItemCount;
  477.  
  478.             checkAndHandleFirstVisibleCellChange();
  479.             checkAndHandleLastVisibleCellChange();
  480.  
  481.             mPreviousFirstVisibleItem = mCurrentFirstVisibleItem;
  482.             mPreviousVisibleItemCount = mCurrentVisibleItemCount;
  483.         }
  484.  
  485.         @Override
  486.         public void onScrollStateChanged(AbsListView view, int scrollState) {
  487.             mCurrentScrollState = scrollState;
  488.             mScrollState = scrollState;
  489.             isScrollCompleted();
  490.         }
  491.  
  492.         /**
  493.          * This method is in charge of invoking 1 of 2 actions. Firstly, if the listview
  494.          * is in a state of scrolling invoked by the hover cell being outside the bounds
  495.          * of the listview, then this scrolling event is continued. Secondly, if the hover
  496.          * cell has already been released, this invokes the animation for the hover cell
  497.          * to return to its correct position after the listview has entered an idle scroll
  498.          * state.
  499.          */
  500.         private void isScrollCompleted() {
  501.             if (mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE) {
  502.                 if (mCellIsMobile && mIsMobileScrolling) {
  503.                     handleMobileCellScroll();
  504.                 } else if (mIsWaitingForScrollFinish) {
  505.                     touchEventsEnded();
  506.                 }
  507.             }
  508.         }
  509.  
  510.         /**
  511.          * Determines if the listview scrolled up enough to reveal a new cell at the
  512.          * top of the list. If so, then the appropriate parameters are updated.
  513.          */
  514.         public void checkAndHandleFirstVisibleCellChange() {
  515.             if (mCurrentFirstVisibleItem != mPreviousFirstVisibleItem) {
  516.                 if (mCellIsMobile && mMobileItemId != INVALID_ID) {
  517.                     updateNeighborViewsForID(mMobileItemId);
  518.                     handleCellSwitch();
  519.                 }
  520.             }
  521.         }
  522.  
  523.         /**
  524.          * Determines if the listview scrolled down enough to reveal a new cell at the
  525.          * bottom of the list. If so, then the appropriate parameters are updated.
  526.          */
  527.         public void checkAndHandleLastVisibleCellChange() {
  528.             int currentLastVisibleItem = mCurrentFirstVisibleItem + mCurrentVisibleItemCount;
  529.             int previousLastVisibleItem = mPreviousFirstVisibleItem + mPreviousVisibleItemCount;
  530.             if (currentLastVisibleItem != previousLastVisibleItem) {
  531.                 if (mCellIsMobile && mMobileItemId != INVALID_ID) {
  532.                     updateNeighborViewsForID(mMobileItemId);
  533.                     handleCellSwitch();
  534.                 }
  535.             }
  536.         }
  537.     };
  538. }
Advertisement
Add Comment
Please, Sign In to add comment