dk.kk.ibikecphlib.controls.SortableListView.java Source code

Java tutorial

Introduction

Here is the source code for dk.kk.ibikecphlib.controls.SortableListView.java

Source

// Copyright (C) 2013 City of Copenhagen.
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at 
// http://mozilla.org/MPL/2.0/.
package dk.kk.ibikecphlib.controls;

import java.util.ArrayList;
import java.util.Iterator;

import android.content.Context;
import android.support.v4.view.GestureDetectorCompat;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.TranslateAnimation;
import android.widget.ListView;

import dk.kk.ibikecphlib.LeftMenu;
import dk.kk.ibikecphlib.favorites.FavoritesAdapter;
import dk.kk.ibikecphlib.util.LOG;
import dk.kk.ibikecphlib.util.Util;

import dk.kk.ibikecphlib.util.Util;

/**
 * A list view that allows for sorting of the list items. Used in the list of favorites.
 * @author jens
 *
 */
public class SortableListView extends ListView {
    private int firstVisibleItem = 0;
    private int viewIndex = 0;
    private int viewHeight;
    private FavoritesAdapter adapter;
    private View view = null;
    LeftMenu parentContainer;
    private int indexToAnimate = -1;
    private int indexToReorder = 0;
    boolean isDragStarted = false;
    ArrayList<ViewData> views;
    float lastY;
    boolean scrolling = false;
    int fixedPositionOffset = 0;
    private GestureDetectorCompat mDetector;
    public int srollPos = 0;

    private void init() {
        mDetector = new GestureDetectorCompat(getContext(), new MyGestureListener());
    }

    public SortableListView(Context context) {
        super(context);
        init();
    }

    public SortableListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public void setParentContainer(LeftMenu parentContainer) {
        this.parentContainer = parentContainer;
    }

    public void enableScroll() {
        getParent().requestDisallowInterceptTouchEvent(false);
    }

    public void disableScroll() {
        getParent().requestDisallowInterceptTouchEvent(true);
        smoothScrollToPosition(0);
        firstVisibleItem = 0;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        mDetector.onTouchEvent(event);

        if ((getAdapter() != null && !(((FavoritesAdapter) getAdapter()).isEditMode)) || getAdapter() == null) {
            LOG.d("sortable list view super.onTouch");
            return super.onTouchEvent(event);
        }
        this.adapter = (FavoritesAdapter) getAdapter();
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            lastDistanceY = 0;
            views = new ArrayList<ViewData>();
            for (int i = 0; i < getAdapter().getCount(); i++) {
                views.add(new ViewData(getChildAt(i), new TranslateAnimation(0, 0, 0, 0)));
            }
            posY = 0;
            movY = 0;
            touchY = event.getY();
            View temp = getChildAt(0);
            temp.measure(0, 0);
            viewHeight = temp.getMeasuredHeight();
            viewIndex = (int) (event.getY() / temp.getMeasuredHeight()) + fixedPositionOffset;
            indexToReorder = viewIndex;
            if (viewIndex < 0 || viewIndex > views.size() - 1) {
                mDetector.onTouchEvent(event);
                return true;
            }
            view = getChildAt(viewIndex); // - firstVisibleItem
            if (event.getX() < Util.dp2px(5) || event.getX() > Util.dp2px(85)) {
                // draging is enabled only when touching the left part of the view
                isDragStarted = false;
            } else {
                isDragStarted = true;
            }
        }

        if (view == null) {
            view = getChildAt(0);
            viewIndex = 0;
        }
        if (isDragStarted) {
            switch (event.getAction()) {
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                adapter.notifyDataSetChanged();
                view.clearAnimation();
                translate(view, 0, true);
                isDragStarted = false;
                if (adapter.getCount() > 1)
                    adapter.reorder(0, 0, true);
                Iterator<ViewData> it = views.iterator();
                while (it.hasNext()) {
                    it.next().animation.cancel();
                }
                if (animation != null)
                    animation.cancel();
                break;
            case MotionEvent.ACTION_DOWN:
                moveCount = 0;
                touchY = event.getY();
                break;

            case MotionEvent.ACTION_MOVE:
                float newTouchY = event.getY();
                float delta = newTouchY - touchY;
                indexToAnimate = (int) (event.getY() / viewHeight) + fixedPositionOffset;
                try {
                    if (indexToReorder != indexToAnimate) {
                        if (indexToAnimate >= 0 && indexToAnimate < adapter.getCount()) {
                            int newY = delta > 0 ? views.get(indexToAnimate).lastY - viewHeight
                                    : views.get(indexToAnimate).lastY + viewHeight;
                            ViewData animateView = views.get(indexToAnimate);
                            if (animateView.animation != null && animateView.animation.isInitialized())
                                animateView.animation.cancel();
                            animateView.animation = new TranslateAnimation(0, 0, animateView.lastY, newY);
                            animateView.lastY = newY;
                            animateView.animation.setFillAfter(true);
                            animateView.animation.setFillBefore(true);
                            animateView.animation.setDuration(500);
                            adapter.reorder(indexToAnimate + firstVisibleItem, indexToReorder + firstVisibleItem,
                                    false);
                            animateView.view.clearAnimation();
                            animateView.view.startAnimation(animateView.animation);
                            ViewData temp = animateView;
                            views.set(indexToAnimate, views.get(indexToReorder));
                            views.set(indexToReorder, temp);
                            indexToReorder = indexToAnimate;
                        }
                    }
                } catch (Exception e) {
                    e.getLocalizedMessage();
                }
                translate(view, delta, false);
                touchY = newTouchY;
                break;
            }
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            final float newY = event.getY();
            if ((movY == 0 && Math.abs(newY - touchY) > viewHeight) || Math.abs(newY - movY) > viewHeight) {
                movY = newY;
            }
        }

        return true;
    }

    TranslateAnimation animation;
    float posY = 0, movY = 0;
    float touchY = 0;
    int maxSlide = 200;
    boolean slidden = false;
    int moveCount = 0;

    void translate(View v, float deltaY, final boolean finalAnim) {
        float newY = posY + deltaY;
        if (animation != null && animation.isInitialized())
            animation.cancel();
        animation = new TranslateAnimation(0, 0, posY, newY);
        animation.setDuration(finalAnim ? 0 : 100);
        animation.setAnimationListener(new AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                if (!finalAnim) {
                    animation.setFillEnabled(true);
                    animation.setFillAfter(true);
                } else {
                    view.clearAnimation();
                    posY = 0;
                }
            }
        });

        posY = newY;

        v.startAnimation(animation);
    }

    public void clearAnimations() {
        if (view != null)
            view.clearAnimation();
        for (int i = 0; i < getChildCount(); i++) {
            getChildAt(i).clearAnimation();
        }
        invalidate();
    }

    private static class ViewData {
        public View view;
        public TranslateAnimation animation;
        public int lastY = 0;

        public ViewData(View view, TranslateAnimation animation) {
            this.view = view;
            this.animation = animation;
        }
    }

    float lastDistanceY = 0;

    class MyGestureListener extends GestureDetector.SimpleOnGestureListener {

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (adapter != null && adapter.getCount() > 1) {
                if (Math.signum(distanceY) != Math.signum(lastDistanceY)) {
                    lastDistanceY = 0;
                }
                lastDistanceY += distanceY;
                LOG.d("scroll diff = " + distanceY);
                if (Math.abs(lastDistanceY) > viewHeight) {
                    int itemsToScroll = (int) (viewHeight / Math.abs(lastDistanceY));
                    if (itemsToScroll == 0) {
                        itemsToScroll = 1;
                    }
                    if (firstVisibleItem != 0 && distanceY < 0) {
                        smoothScrollToPosition(firstVisibleItem - itemsToScroll);
                        firstVisibleItem -= itemsToScroll;
                    } else if (firstVisibleItem != adapter.getCount() - 1 && distanceY > 0) {
                        smoothScrollToPosition(firstVisibleItem + getChildCount() + itemsToScroll - 1);
                        firstVisibleItem += itemsToScroll;
                    }

                    if (firstVisibleItem < 0) {
                        firstVisibleItem = 0;
                    }
                    if (firstVisibleItem > adapter.getCount() - getChildCount()) {
                        firstVisibleItem = adapter.getCount() - getChildCount();
                    }
                    lastDistanceY = 0;
                }
            }
            return true;
        };

        @Override
        public boolean onDown(MotionEvent event) {
            return true;
        }

        @Override
        public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) {
            LOG.d("fling");
            if (adapter != null && adapter.getCount() > 1) {
                int itemsToScroll = (int) (viewHeight / Math.abs(velocityY));
                if (firstVisibleItem != 0 && velocityY > 0) {
                    smoothScrollToPosition(firstVisibleItem - itemsToScroll);
                    firstVisibleItem -= itemsToScroll;
                } else if (firstVisibleItem != adapter.getCount() - 1 && velocityY < 0) {
                    smoothScrollToPosition(firstVisibleItem + getChildCount() + itemsToScroll - 1);
                    firstVisibleItem += itemsToScroll;
                }
                if (firstVisibleItem < 0) {
                    firstVisibleItem = 0;
                }
                if (firstVisibleItem > adapter.getCount() - getChildCount()) {
                    firstVisibleItem = adapter.getCount() - getChildCount();
                }
            }
            return true;
        }
    }

}