Android Open Source - SyncedListView Synced List Layout






From Project

Back to project page SyncedListView.

License

The source code is released under:

Apache License

If you think the Android project SyncedListView listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright 2013 Wenhui Yao//from   w  ww .  ja  v  a2  s.  c  o  m
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wenhui.syncedListView.lib;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.MotionEventCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.LinearInterpolator;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.OverScroller;
import android.widget.Scroller;


public class SyncedListLayout extends LinearLayout {

  private static final String TAG = "SyncedListLayout";

    private static final int TOUCH_MODE_FLING = 1;
    private static final int TOUCH_MODE_SCROLL = 2;
    private static final int TOUCH_MODE_REST = 0;

    private static final int DEFAULT_SCROLL_ANIMATION_DURATION = 60*1000; // MINUTE
    private static final int DEFAULT_VELOCITY = 1500;  // PER MINUTE
    private static final long DEFAULT_ANIMATION_DELAY = 10L;

    private ListView mListViewLeft;
    private ListView mListViewRight;
    private GestureDetectorCompat gestureDetector;
    private int mLastFlingY = 0;
    private float mRightScrollFactor = 0.8f;
    private float mLeftScrollFactor = 1.4f;
    private MotionEvent mDownEvent;
    private boolean mAnimating=false;
    private boolean mRequestStopAnim = false;
    private FlingRunnable mFlingRunnable;
    private AnimationRunnable mAnimationRunnable;
    private int mAnimationDuration = DEFAULT_SCROLL_ANIMATION_DURATION;
    private int mAnimationVelocity = DEFAULT_VELOCITY;
    private int mLeftListId=0, mRightListId=0;
    private float mLeftAnimationScrollFactor = 2f, mRightAnimationScrollFactor=1f;
    private Scroller mScroller;
    private int mTouchMode = TOUCH_MODE_REST;


    public SyncedListLayout(Context context) {
        super(context);
        init(context, null);
    }

    public SyncedListLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

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

    private void init(Context context, AttributeSet attrs ) {

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SyncedListLayout);
        try{
            mLeftListId = a.getResourceId(R.styleable.SyncedListLayout_left_id, 0);
            mRightListId = a.getResourceId(R.styleable.SyncedListLayout_right_id, 0);
            mLeftScrollFactor = a.getFloat(R.styleable.SyncedListLayout_left_scroll_factor, 1f);
            mRightScrollFactor = a.getFloat(R.styleable.SyncedListLayout_right_scroll_factor, 1f);
        }finally {
            a.recycle();
        }

        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics metric = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(metric);
        mAnimationVelocity = (int)(DEFAULT_VELOCITY * metric.density);

        gestureDetector = new GestureDetectorCompat(context, gestureListener);
        mScroller = new Scroller(context);

        mAnimationRunnable = new AnimationRunnable(context);
        mFlingRunnable = new FlingRunnable();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        stopAnimation();
        removeCallbacks(mAnimationRunnable);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mListViewLeft = (ListView) findViewById(mLeftListId);
        mListViewRight = (ListView) findViewById(mRightListId);

        if( mListViewRight == null || mListViewLeft == null ){
            throw new IllegalStateException("Either left list or right list cannot be null");
        }

    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
      return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean handle = gestureDetector.onTouchEvent(event);
        int action = MotionEventCompat.getActionMasked(event);
        switch( action ){
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "touch mode " + mTouchMode);
                if( mTouchMode != TOUCH_MODE_FLING ){
                    startAnimationInternal(DEFAULT_ANIMATION_DELAY);
                }

                mTouchMode = TOUCH_MODE_REST;
                break;
        }
        return handle;
    }
    
  private void dispatchTouchToList(final MotionEvent e){
    if( mDownEvent == null ){ return; }

        int leftListWidth = mListViewLeft.getWidth();

        if (mDownEvent.getX() <= mListViewLeft.getWidth()) {
            mListViewLeft.dispatchTouchEvent(mDownEvent);
            mListViewLeft.dispatchTouchEvent(e);
        } else {
            // For some reason, this will only recognize x of left list
            mDownEvent.offsetLocation(-leftListWidth, 0f);
            e.offsetLocation(-leftListWidth, 0f);
            mListViewRight.dispatchTouchEvent(mDownEvent);
            mListViewRight.dispatchTouchEvent(e);
        }

        mDownEvent.recycle();
        mDownEvent = null;
  }

    @Override
    public void computeScroll() {
        if ( mScroller.computeScrollOffset() ){

            if( mAnimating ){ stopAnimationInternal(); }

            int curY = mScroller.getCurrY();
            int distance = curY - mLastFlingY;
            mLastFlingY = curY;

            if( distance != 0 ){
                mFlingRunnable.setDistance(distance);
                post(mFlingRunnable);
            }

            if ( mScroller.isFinished() || distance == 0 ) {
                startAnimationInternal(DEFAULT_ANIMATION_DELAY);
            }

        }

    }

    public void setLeftListView(ListView left){
        this.mListViewLeft = left;
    }

    public void setRightListView(ListView right){
        this.mListViewRight = right;
    }

    public void setLeftAnimationScrollFactor(float factor){
        this.mLeftAnimationScrollFactor = factor;
    }

    public void setRightAnimationScrollFactor(float factor){
        this.mRightAnimationScrollFactor= factor;
    }

    public boolean isAnimating() {
        return mAnimating;
    }

    public void startAnimation(long delay) {
        mRequestStopAnim = false;
        startAnimationInternal(delay);
    }

    private void startAnimationInternal(long delay){
        if( mAnimating ){ return; }

        if(mRequestStopAnim ){ return; }

        mAnimating = true;
        postDelayed(mAnimationLaunchRunnable, delay);
    }

    private Runnable mAnimationLaunchRunnable = new Runnable() {
        @Override
        public void run() {
            mAnimationRunnable.startAnimation(mAnimationVelocity, mAnimationDuration);
        }
    };

    public void stopAnimation(){
        mRequestStopAnim = true;
        stopAnimationInternal();
    }

    private void stopAnimationInternal(){
        mAnimating = false;
        removeCallbacks(mAnimationLaunchRunnable);
        mAnimationRunnable.cancel();
    removeCallbacks(mAnimationRunnable);
    }

    public void setLeftScrollFactor(float factor){
        this.mLeftScrollFactor = factor;
    }

    public void setRightScrollFactor(float factor){
        this.mRightScrollFactor = factor;
    }

    /**
     *
     * @param velocity  Distance per second
     */
    public void setAnimationVelocity(int velocity){
        this.mAnimationVelocity = velocity * 60;
    }

    private class FlingRunnable implements Runnable {
        private float distance = 0;

        public void setDistance(float distance){
            this.distance = distance;
        }

        @Override
        public void run() {
            scrollListBy(distance, mLeftScrollFactor, mRightScrollFactor);
        }
    }

    private void scrollListBy(float distance, float leftScrollFactor, float rightScrollFactor){
        scrollListBy(mListViewRight, (int) (distance * rightScrollFactor + 0.5f));
        scrollListBy(mListViewLeft, (int) (distance * leftScrollFactor + 0.5f));
    }

    private void scrollListBy(ListView target, int deltaY) {
        final int firstPosition = target.getFirstVisiblePosition();
        if (firstPosition == ListView.INVALID_POSITION) {
            return;
        }

        final View firstView = target.getChildAt(0);
        if( firstView == null ){ return; }

        final int newTop = firstView.getTop() - deltaY;
        target.setSelectionFromTop(firstPosition, newTop);
    }

    private GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {

        @Override
        public boolean onDown(MotionEvent e) {
      mScroller.forceFinished(true);  
      if( mDownEvent != null ){
        mDownEvent.recycle();
      }
      mDownEvent = MotionEvent.obtain(e);
      removeCallbacks(mFlingRunnable);
      stopAnimationInternal();
            return true;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            mTouchMode = TOUCH_MODE_FLING;
            mLastFlingY = mListViewRight.getScrollY();
            mScroller.fling(0, mLastFlingY, 0, (int)-velocityY, Integer.MIN_VALUE, Integer.MAX_VALUE,
                Integer.MIN_VALUE, Integer.MAX_VALUE);

            return true;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            mTouchMode = TOUCH_MODE_SCROLL;
          mFlingRunnable.setDistance(distanceY);
          post(mFlingRunnable);
            return true;
        }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
            dispatchTouchToList(e);
      return false;
    }

    };

    private class AnimationRunnable implements Runnable {
        private int lastY;
        private final OverScroller scroller;
        private int distance, duration;
        private boolean cancelled = false;

        public AnimationRunnable(Context context){
            scroller = new OverScroller(context, new LinearInterpolator());
        }

        public void startAnimation(int distance, int duration){
            cancelled = false;
            this.distance = distance;
            this.duration = duration;
            lastY = mListViewRight.getScrollY();
            animate();
        }

        private void animate(){
            scroller.startScroll(0, lastY, 0, distance, duration);
            post(this);
        }

        public void cancel(){
            cancelled = true;
            scroller.forceFinished(true);
        }

        @Override
        public void run() {
            if( cancelled ){
                return;
            }

            boolean hasMore = scroller.computeScrollOffset();
            int y = scroller.getCurrY();
            int yDiff = y - lastY;
            if( yDiff != 0 ){
                scrollListBy(yDiff, mLeftAnimationScrollFactor, mRightAnimationScrollFactor);
                lastY = y;
            }

            if( hasMore ){
                post(this);
            } else {
                animate();
            }

        }
    }


}




Java Source Code List

com.wenhui.syncedListView.demo.demo.Images.java
com.wenhui.syncedListView.demo.demo.SyncListViewActivity.java
com.wenhui.syncedListView.demo.demo.SyncListViewContainerFragment.java
com.wenhui.syncedListView.lib.InfiniteListAdapter.java
com.wenhui.syncedListView.lib.SyncedListLayout.java