@Override
  public View getView(int position, View convertView, ViewGroup parent) {
    Holder holder;
    View view = convertView;

    if (view == null) {
      view = LayoutInflater.from(context).inflate(R.layout.grid_item, parent, false);
      holder = new Holder();
      holder.title = (TextView) view.findViewById(R.id.grid_item_title);
      holder.cover = (ImageView) view.findViewById(R.id.grid_item_cover);
      view.setTag(holder);
    } else {
      holder = (Holder) view.getTag();
    }

    GridItem item = list.get(position);
    holder.title.setText(item.getTitle());
    holder.cover.setImageBitmap(BrowserUnit.file2Bitmap(context, item.getFilename()));
    ViewUnit.setElevation(
        view, context.getResources().getDimensionPixelSize(R.dimen.elevation_1dp));

    return view;
  }
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    if (!callback.canSwipe()) return false;

    event.offsetLocation(translationX, 0);
    if (targetWidth < 2) targetWidth = view.getWidth();

    switch (event.getActionMasked()) {
      case MotionEvent.ACTION_DOWN:
        {
          downX = event.getRawX();

          velocityTracker = VelocityTracker.obtain();
          velocityTracker.addMovement(event);

          return false;
        }
      case MotionEvent.ACTION_UP:
        {
          if (velocityTracker == null) break;

          velocityTracker.addMovement(event);
          velocityTracker.computeCurrentVelocity(1000);

          if (swiping) {
            view.animate()
                .translationX(0f)
                .setDuration(animTime)
                .setListener(
                    new AnimatorListenerAdapter() {
                      @Override
                      public void onAnimationEnd(Animator animation) {
                        callback.onBound(canSwitch, swipingLeft);
                      }
                    });
          }

          downX = 0;
          translationX = 0;
          swiping = false;
          velocityTracker.recycle();
          velocityTracker = null;

          break;
        }
      case MotionEvent.ACTION_CANCEL:
        {
          if (velocityTracker == null) {
            break;
          }

          view.animate().translationX(0f).setDuration(animTime).setListener(null);

          downX = 0;
          translationX = 0;
          swiping = false;
          velocityTracker.recycle();
          velocityTracker = null;

          break;
        }
      case MotionEvent.ACTION_MOVE:
        {
          if (velocityTracker == null) {
            break;
          }

          velocityTracker.addMovement(event);

          float deltaX = event.getRawX() - downX;
          if (Math.abs(deltaX) > slop) {
            swiping = true;
            swipingLeft = deltaX < 0;
            canSwitch =
                Math.abs(deltaX)
                    >= ViewUnit.dp2px(
                        view.getContext(),
                        48); // Can switch tabs when deltaX >= 48 to prevent misuse
            swipingSlop = (deltaX > 0 ? slop : -slop);
            view.getParent().requestDisallowInterceptTouchEvent(true);

            MotionEvent cancelEvent = MotionEvent.obtainNoHistory(event);
            cancelEvent.setAction(
                MotionEvent.ACTION_CANCEL
                    | (event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
            view.onTouchEvent(cancelEvent);
            cancelEvent.recycle();
          }

          if (swiping) {
            translationX = deltaX;
            view.setTranslationX(deltaX - swipingSlop);
            callback.onSwipe();

            return true;
          }

          break;
        }
    }

    return false;
  }