Android Open Source - vitdroid-android Scroll Layout






From Project

Back to project page vitdroid-android.

License

The source code is released under:

Apache License

If you think the Android project vitdroid-android 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 (C) 2011 Daniel Berndt - Codeus Ltd  -  DateSlider
 */*  w  w w.  j  a va 2s. c o m*/
 * This class contains all the scrolling logic of the slidable elements
 *
 * 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.googlecode.android.widgets.DateSlider;

import java.lang.reflect.Constructor;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.Scroller;

import com.googlecode.android.widgets.DateSlider.labeler.Labeler;
import com.googlecode.android.widgets.DateSlider.timeview.TimeView;

/**
 * This is where most of the magic happens. This is a subclass of LinearLayout
 * that display a collection of TimeViews and handles the scrolling, shuffling
 * the TimeViews around to keep the display up-to-date, and managing the Labelers
 * to populate the TimeViews with the correct data.
 *
 * This class is configured via xml attributes that specify the class of the
 * labeler to use to generate views, the format string for the labeler to use
 * to populate the views, and optionally width and height values to override
 * the default width and height of the views.
 */
public class ScrollLayout extends LinearLayout {

    private static String TAG = "SCROLLLAYOUT";

    private Scroller mScroller;
    /**
     * Indicates if we are currently tracking touch events that are dragging
     * (scrolling) us.
     */
    private boolean mDragMode;
    /**
     * The aggregate width of all of our children
     */
    private int childrenWidth;
    /**
     * The aggregate width of our children is very likely to be wider than the
     * bounds of our view. Since we keep everything centered, we need to keep
     * our view scrolled by enough to center our children, rather than
     * left-aligning them. This variable tracks how much to scroll to achieve this.
     */
    private int mInitialOffset;
    private int mLastX, mLastScroll, mScrollX;
    private VelocityTracker mVelocityTracker;
    private int mMinimumVelocity, mMaximumVelocity;
    /**
     * The time that we are currently displaying
     */
    private long currentTime = System.currentTimeMillis();
    private long minTime = -1, maxTime = -1;
    private int minuteInterval = 1;
    
    /**
     * The width of each child
     */
    private int objWidth;
    /**
     * The height of each child
     */
    private int objHeight;
    
    private Drawable rightShadow, leftShadow;

    private Labeler mLabeler;
    private OnScrollListener listener;
    private TimeView mCenterView;

    public ScrollLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.setWillNotDraw(false);
        rightShadow = getContext().getResources().getDrawable(R.drawable.right_shadow);
        leftShadow = getContext().getResources().getDrawable(R.drawable.left_shadow);
        mScroller = new Scroller(getContext());
        setGravity(Gravity.CENTER_VERTICAL);
        setOrientation(HORIZONTAL);
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        // as mMaximumVelocity does not exist in API<4
        float density = getContext().getResources().getDisplayMetrics().density;
        mMaximumVelocity = (int)(4000 * 0.5f * density);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ScrollLayout,
                0, 0);

        // Get the labeler class and construct an instance
        String className = a.getNonResourceString(R.styleable.ScrollLayout_labelerClass);
        if (className == null) {
            throw new RuntimeException("Must specify labeler class at " + a.getPositionDescription());
        }

        String labelerFormat = a.getString(R.styleable.ScrollLayout_labelerFormat);
        if (labelerFormat == null) {
            throw new RuntimeException("Must specify labelerFormat at " + a.getPositionDescription());
        }

        try {
            Class<?> klazz = Class.forName(className);
            Constructor<?> ctor = klazz.getConstructor(String.class);
            mLabeler = (Labeler)ctor.newInstance(labelerFormat);
        } catch (Exception e) {
            throw new RuntimeException("Failed to construct labeler at " + a.getPositionDescription(), e);
        }

        // Determine the width and height of our children, using the labelers preferred
        // values as defaults
        objWidth = a.getDimensionPixelSize(R.styleable.ScrollLayout_childWidth,
                mLabeler.getPreferredViewWidth(context));
        objHeight = a.getDimensionPixelSize(R.styleable.ScrollLayout_childHeight,
                mLabeler.getPreferredViewHeight(context));

        a.recycle();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        //
        // We need to generate enough children to fill all of our desired space, and
        // it needs to be an odd number of children because we treat the center view
        // specially. So, first compute how many children we will need.
        //
        final Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        final int displayWidth = display.getWidth();
        int childCount = displayWidth / objWidth;
        // Make sure to round up
        if (displayWidth % objWidth != 0) {
            childCount++;
        }
        // Now make sure we have an odd number of children
        if (childCount % 2 == 0) {
            childCount++;
        }

        // We have an odd number of children, so childCount / 2 will round down to the
        // index just before the center in 1-based indexing, meaning that it will be the
        // center index in 0-based indexing.
        final int centerIndex = (childCount / 2);

        // Make sure we weren't inflated with any views for some odd reason
        removeAllViews();

        // Now add all of the child views, making sure to make the center view as such.
        for (int i = 0; i < childCount; i++) {
            LayoutParams lp = new LayoutParams(objWidth, objHeight);
            TimeView ttv = mLabeler.createView(getContext(), i == centerIndex);
            addView((View)ttv, lp);
        }

        // Now we need to set the times on all of the TimeViews. We start with the center
        // view, work our way to the end, then starting from the center again, work our
        // way back to the beginning.
        mCenterView = (TimeView)getChildAt(centerIndex);
        mCenterView.setVals(mLabeler.getElem(currentTime));
        
        
        Log.v(TAG, "mCenter: " + mCenterView.getTimeText() + " minInterval " + minuteInterval);

        // TODO: Do I need to use endTime, or can I just use the point time?
        for (int i = centerIndex + 1; i < childCount; i++) {
            TimeView lastView = (TimeView)getChildAt(i - 1);
            TimeView thisView = (TimeView)getChildAt(i);
            thisView.setVals(mLabeler.add(lastView.getEndTime(), 1));
        }
        for (int i = centerIndex - 1; i >= 0; i--) {
            TimeView lastView = (TimeView)getChildAt(i + 1);
            TimeView thisView = (TimeView)getChildAt(i);
            thisView.setVals(mLabeler.add(lastView.getEndTime(), -1));
        }

        // Finally, set our actual children width
        childrenWidth = childCount * objWidth;
    }

    @Override
    public void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // In order to keep our children centered, the initial offset has to
        // be half the difference between our childrens' width and our width.
        mInitialOffset = (childrenWidth-w)/2;
        // Now scroll to that offset
        super.scrollTo(mInitialOffset, 0);
        mScrollX = mInitialOffset;
        mLastScroll = mInitialOffset;
        setTime(currentTime,0);
    }
    
    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        rightShadow.setBounds(getScrollX()+getWidth()-50, 0, getWidth()+getScrollX()+1, getHeight());
        rightShadow.draw(canvas);
        
        leftShadow.setBounds(getScrollX(), 0, getScrollX()+50, getHeight());
        leftShadow.draw(canvas);
    }

    public void setMinTime(long time) {
      minTime = time;
    }
    
    public void setMaxTime(long time) {
      maxTime = time;
    }
    
    
    public void setTime(long time) {
      this.setTime(time, 0);
    }
    
    
    /**
     * sets a new minuteInterval
     * this requires us to update all the views, because they are still working with the old
     * minuteInterval
     * 
     * @param minInterval
     */
    public void setMinuteInterval(int minInterval) {
      this.minuteInterval = minInterval;
      mLabeler.setMinuteInterval(minInterval);
      if (minInterval>1) {
        final int centerIndex = (getChildCount() / 2);
            for (int i = centerIndex + 1; i < getChildCount(); i++) {
                TimeView lastView = (TimeView)getChildAt(i - 1);
                TimeView thisView = (TimeView)getChildAt(i);
                thisView.setVals(mLabeler.add(lastView.getEndTime(), 1));
            }
            for (int i = centerIndex - 1; i >= 0; i--) {
                TimeView lastView = (TimeView)getChildAt(i + 1);
                TimeView thisView = (TimeView)getChildAt(i);
                thisView.setVals(mLabeler.add(lastView.getEndTime(), -1));
            }
      }
    }
    
    /**
     * this element will position the TimeTextViews such that they correspond to the given time
     * @param time
     * @param loops prevents setTime getting called too often, if loop is > 2 the procedure will be
     * stopped
     */
    private void setTime(long time, int loops) {
        currentTime = time;
        if (!mScroller.isFinished()) mScroller.abortAnimation();
        int pos = getChildCount()/2;
        TimeView currelem = (TimeView)getChildAt(pos);
        if (loops>2 || currelem.getStartTime() <= time && currelem.getEndTime() >= time) {
            if (loops>2) {
                Log.d(TAG,String.format("time: %d, start: %d, end: %d", time, currelem.getStartTime(), currelem.getEndTime()));
                return;
            }
            double center = getWidth()/2.0;
            int left = (getChildCount()/2)*objWidth-getScrollX();
            double currper = (center-left)/objWidth;
            double goalper = (time-currelem.getStartTime())/(double)(currelem.getEndTime()-currelem.getStartTime());
            int shift = (int)Math.round((currper-goalper)*objWidth);
            mScrollX-=shift;
            reScrollTo(mScrollX,0,false);
        } else {
            double diff = currelem.getEndTime() - currelem.getStartTime();
            int steps = (int)Math.round(((time-(currelem.getStartTime()+diff/2))/diff));
            moveElements(-steps);
            setTime(time, loops+1);
        }
    }



    /**
     * scroll the element when the mScroller is still scrolling
     */
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mScrollX = mScroller.getCurrX();
            reScrollTo(mScrollX,0, true);
            // Keep on drawing until the animation has finished.
            postInvalidate();
        }
    }

    @Override
    public void scrollTo(int x, int y) {
        if (!mScroller.isFinished()) {
            mScroller.abortAnimation();
        }
        reScrollTo(x, y, true);
    }

    /**
     * core scroll function which will replace and move TimeTextViews so that they don't get
     * scrolled out of the layout
     *
     * @param x
     * @param y
     * @param notify if false, the listeners won't be called
     */
    protected void reScrollTo(int x, int y, boolean notify) {
      if (notify) Log.d(TAG,String.format("scroll to " + x));
      int scrollX = getScrollX();
      int scrollDiff = x - mLastScroll;
      
      // estimate whether we are going to reach the lower limit
      if (minTime!=-1 && notify && scrollDiff<0) {
            double center = getWidth()/2.0;
            int left = (getChildCount()/2)*objWidth-scrollX;
            double f = (center-left)/objWidth;
            
        long esp_time = (long) (mCenterView.getStartTime() + (f - ((double)-scrollDiff)/objWidth) * (mCenterView.getEndTime() - mCenterView.getStartTime()));
        
        // if we reach it, prevent surpassing it
        if (esp_time<minTime) {
          int deviation = scrollDiff - (int) Math.round(((double) (currentTime - minTime))/(currentTime - esp_time) * scrollDiff);
          mScrollX -= deviation;
          x -= deviation;
          scrollDiff -= deviation;
          if (!mScroller.isFinished()) mScroller.abortAnimation();
        }
      }
      // estimate whether we are going to reach the upper limit
      else if (maxTime!=-1 && notify && scrollDiff>0) {
        double center = getWidth()/2.0;
            int left = (getChildCount()/2)*objWidth-scrollX;
            double f = (center-left)/objWidth;
            
        long esp_time = (long) (mCenterView.getStartTime() + (f - ((double)-scrollDiff)/objWidth) * (mCenterView.getEndTime() - mCenterView.getStartTime()));
        
        // if we reach it, prevent surpassing it
        if (esp_time>maxTime) {
          int deviation = scrollDiff - (int) Math.round(((double) (currentTime - maxTime))/(currentTime - esp_time) * scrollDiff);
          mScrollX -= deviation;
          x -= deviation;
          scrollDiff -= deviation;
          if (!mScroller.isFinished()) mScroller.abortAnimation();
        }
      }      
        
        if (getChildCount()>0) {
            // Determine the absolute x-value for where we are being asked to scroll
            scrollX += scrollDiff;
            // If we've scrolled more than half of a view width in either direction, then
            // a different time is the "current" time, and we need to shuffle our views around.
            // Each additional full view's width on top of the initial half view's width is
            // another position that we need to move our elements. So, we need to add half the
            // width to the amount we've scrolled and then compute how many full multiples of
            // the view width that encompasses to determine how far to move our elements.
            if (scrollX - mInitialOffset > objWidth/2) {
                // Our scroll target relative to our initial offset
                int relativeScroll = scrollX - mInitialOffset;
                int stepsRight = (relativeScroll + (objWidth/2)) / objWidth;
                moveElements(-stepsRight);
                // Now modify the scroll target based on our view shuffling.
                scrollX = ((relativeScroll-objWidth/2) % objWidth)+mInitialOffset-objWidth/2;
            } else if (mInitialOffset - scrollX > objWidth/2) {
                int relativeScroll = mInitialOffset - scrollX;
                int stepsLeft = (relativeScroll + (objWidth / 2)) / objWidth;
                moveElements(stepsLeft);
                scrollX = (mInitialOffset + objWidth/2 - ((mInitialOffset+objWidth/2-scrollX)%objWidth));
            }
        }
        super.scrollTo(scrollX,y);
        if (listener!=null && notify) {
            double center = getWidth()/2.0;
            int left = (getChildCount()/2)*objWidth-scrollX;
            double f = (center-left)/objWidth;
            currentTime = (long)(mCenterView.getStartTime()+(mCenterView.getEndTime()-mCenterView.getStartTime())*f);
            if (notify) Log.d(TAG,String.format("real time " + currentTime));
            if (notify) Log.d(TAG,String.format(""));
            listener.onScroll(currentTime);
        };
        mLastScroll = x;
    }

    /**
     * when the scrolling procedure causes "steps" elements to fall out of the visible layout,
     * all TimeTextViews swap their contents so that it appears that there happens an endless
     * scrolling with a very limited amount of views
     *
     * @param steps
     */
    protected void moveElements(int steps) {
        if (steps == 0) {
            return;
        }

        // We need to make each TimeView reflect a value that is -steps units
        // from its current value. As an optimization, we will see if this
        // value is already present in another child (by looking to see if there
        // is a child at an index -steps offset from the target child's index).
        // Since this method is most often called with steps equal to 1 or -1,
        // this is a valuable optimization. However, when doing this we need to
        // make sure that we don't overwrite the value of the other child before
        // we copy the value out. So, when steps is negative, we will be pulling
        // values from children with larger indexes and we want to iterate forwards.
        // When steps is positive, we will be pulling values from children with
        // smaller indexes, and we want to iterate backwards.

        int start;
        int end;
        int incr;
        if (steps < 0) {
            start = 0;
            end = getChildCount();
            incr = 1;
        } else {
            start = getChildCount() - 1;
            end = -1;
            incr = -1;
        }
        for (int i = start; i != end; i += incr) {
            TimeView tv = (TimeView)getChildAt(i);
            int index = i - steps;
            if (index >= 0 && index < getChildCount()) {
                tv.setVals((TimeView)getChildAt(index));
            } else {
                tv.setVals(mLabeler.add(tv.getEndTime(), -steps));
            }
            if (minTime != -1 && tv.getEndTime() < minTime) {
              if (!tv.isOutOfBounds()) tv.setOutOfBounds(true);
            } else if (maxTime != -1 && tv.getStartTime() > maxTime) {
              if (!tv.isOutOfBounds()) tv.setOutOfBounds(true);
            } else if (tv.isOutOfBounds()) {
              tv.setOutOfBounds(false);
            }
        }
    }

    /**
     * finding whether to scroll or not
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        final int x = (int) ev.getX();
        if (action == MotionEvent.ACTION_DOWN) {
            mDragMode = true;
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }
        }

        if (!mDragMode)
            return super.onTouchEvent(ev);

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        switch (action) {
        case MotionEvent.ACTION_DOWN:
            break;
        case MotionEvent.ACTION_MOVE:
            mScrollX += mLastX - x;
            reScrollTo(mScrollX, 0, true);
            break;
        case MotionEvent.ACTION_UP:
            final VelocityTracker velocityTracker = mVelocityTracker;
            velocityTracker.computeCurrentVelocity(1000);
            int initialVelocity = (int) Math.min(velocityTracker.getXVelocity(), mMaximumVelocity);

            if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) {
                fling(-initialVelocity);
            }
        case MotionEvent.ACTION_CANCEL:
        default:
            mDragMode = false;

        }
        mLastX = x;

        return true;
    }

    /**
     * causes the underlying mScroller to do a fling action which will be recovered in the
     * computeScroll method
     * @param velocityX
     */
    private void fling(int velocityX) {
        if (getChildCount() > 0) {
            mScroller.fling(mScrollX, 0, velocityX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
            invalidate();
        }
    }

    public void setOnScrollListener(OnScrollListener l) {
        listener = l;
    }

    public interface OnScrollListener {
        public void onScroll(long x);
    }
}




Java Source Code List

com.googlecode.android.widgets.DateSlider.AlternativeDateSlider.java
com.googlecode.android.widgets.DateSlider.Attendance.java
com.googlecode.android.widgets.DateSlider.Attendanceboth.java
com.googlecode.android.widgets.DateSlider.BackGroundDialogs.java
com.googlecode.android.widgets.DateSlider.Base64.java
com.googlecode.android.widgets.DateSlider.CustomDateSlider.java
com.googlecode.android.widgets.DateSlider.DBAdapter2.java
com.googlecode.android.widgets.DateSlider.DBAdapter3.java
com.googlecode.android.widgets.DateSlider.DBAdapter5.java
com.googlecode.android.widgets.DateSlider.DBAdapter6.java
com.googlecode.android.widgets.DateSlider.DBAdapter.java
com.googlecode.android.widgets.DateSlider.DBAdaptergrades.java
com.googlecode.android.widgets.DateSlider.DBAdapterlinks.java
com.googlecode.android.widgets.DateSlider.DateSlider.java
com.googlecode.android.widgets.DateSlider.DateTimeSlider.java
com.googlecode.android.widgets.DateSlider.DefaultDateSlider.java
com.googlecode.android.widgets.DateSlider.Demo.java
com.googlecode.android.widgets.DateSlider.FacultyCabin.java
com.googlecode.android.widgets.DateSlider.Faculty.java
com.googlecode.android.widgets.DateSlider.GAttendance.java
com.googlecode.android.widgets.DateSlider.GDBAdapter5.java
com.googlecode.android.widgets.DateSlider.GDBAdapter6.java
com.googlecode.android.widgets.DateSlider.GDBAdapter.java
com.googlecode.android.widgets.DateSlider.GFaculty.java
com.googlecode.android.widgets.DateSlider.GMarks.java
com.googlecode.android.widgets.DateSlider.GStudent.java
com.googlecode.android.widgets.DateSlider.GuestLogin.java
com.googlecode.android.widgets.DateSlider.GuestMainscreen.java
com.googlecode.android.widgets.DateSlider.Login.java
com.googlecode.android.widgets.DateSlider.Mainscreen.java
com.googlecode.android.widgets.DateSlider.MarkAttendance.java
com.googlecode.android.widgets.DateSlider.Marks.java
com.googlecode.android.widgets.DateSlider.MinimalDemo.java
com.googlecode.android.widgets.DateSlider.MonthYearDateSlider.java
com.googlecode.android.widgets.DateSlider.MyService2.java
com.googlecode.android.widgets.DateSlider.MyService4.java
com.googlecode.android.widgets.DateSlider.MyService5.java
com.googlecode.android.widgets.DateSlider.MyService.java
com.googlecode.android.widgets.DateSlider.ObservableScrollView2.java
com.googlecode.android.widgets.DateSlider.ObservableScrollView.java
com.googlecode.android.widgets.DateSlider.QuizRem3.java
com.googlecode.android.widgets.DateSlider.QuizRem4.java
com.googlecode.android.widgets.DateSlider.Quiz.java
com.googlecode.android.widgets.DateSlider.Refresh.java
com.googlecode.android.widgets.DateSlider.SMainScreen.java
com.googlecode.android.widgets.DateSlider.ScrollLayout.java
com.googlecode.android.widgets.DateSlider.ScrollViewListener2.java
com.googlecode.android.widgets.DateSlider.ScrollViewListener.java
com.googlecode.android.widgets.DateSlider.ServiceManager.java
com.googlecode.android.widgets.DateSlider.Settings.java
com.googlecode.android.widgets.DateSlider.SimpleGestureFilter.java
com.googlecode.android.widgets.DateSlider.SliderContainer.java
com.googlecode.android.widgets.DateSlider.Student.java
com.googlecode.android.widgets.DateSlider.TimeObject.java
com.googlecode.android.widgets.DateSlider.TimeSlider.java
com.googlecode.android.widgets.DateSlider.ViewflipActivity.java
com.googlecode.android.widgets.DateSlider.labeler.DayDateLabeler.java
com.googlecode.android.widgets.DateSlider.labeler.DayLabeler.java
com.googlecode.android.widgets.DateSlider.labeler.HourLabeler.java
com.googlecode.android.widgets.DateSlider.labeler.Labeler.java
com.googlecode.android.widgets.DateSlider.labeler.MinuteLabeler.java
com.googlecode.android.widgets.DateSlider.labeler.MonthLabeler.java
com.googlecode.android.widgets.DateSlider.labeler.MonthYearLabeler.java
com.googlecode.android.widgets.DateSlider.labeler.TimeLabeler.java
com.googlecode.android.widgets.DateSlider.labeler.Util.java
com.googlecode.android.widgets.DateSlider.labeler.WeekLabeler.java
com.googlecode.android.widgets.DateSlider.labeler.YearLabeler.java
com.googlecode.android.widgets.DateSlider.timeview.DayTimeLayoutView.java
com.googlecode.android.widgets.DateSlider.timeview.TimeLayoutView.java
com.googlecode.android.widgets.DateSlider.timeview.TimeTextView.java
com.googlecode.android.widgets.DateSlider.timeview.TimeView.java
com.googlecode.android.widgets.DateSlider.global.java
com.googlecode.android.widgets.DateSlider.grades.java
com.googlecode.android.widgets.DateSlider.notif2.java
com.googlecode.android.widgets.DateSlider.notif.java
com.googlecode.android.widgets.DateSlider.pbl.java
com.googlecode.android.widgets.DateSlider.sample.java
in.ac.vit.vitdroid.DBAdaptergrades.java
in.ac.vit.vitdroid.DBAdapterlinks.java