/*
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* 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.google.android.apps.tvremote.layout;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.Scroller;
/**
* SlidingLayout that allows swapping between arbitrary number of child views.
*/
public final class SlidingLayout extends ViewGroup {
private static final int INVALID_SCREEN = -1;
private int mDefaultScreen;
private boolean mFirstLayout = true;
private int mCurrentScreen;
private int mNextScreen = INVALID_SCREEN;
private Scroller mScroller;
private WorkspaceOvershootInterpolator mScrollInterpolator;
/**
* Tension interpolator for nice scroll animation.
*/
private static class WorkspaceOvershootInterpolator implements Interpolator {
private static final float DEFAULT_TENSION = 1.3f;
private float mTension;
public WorkspaceOvershootInterpolator() {
mTension = DEFAULT_TENSION;
}
public void disableSettle() {
mTension = 0.f;
}
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * ((mTension + 1) * t + mTension) + 1.0f;
}
}
/**
* Used to inflate the Workspace from XML.
*
* @param context
* The application's context.
* @param attrs
* The attribtues set containing the Workspace's customization
* values.
*/
public SlidingLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Used to inflate the Workspace from XML.
*
* @param context
* The application's context.
* @param attrs
* The attribtues set containing the Workspace's customization
* values.
* @param defStyle
* Unused.
*/
public SlidingLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initSlidingLayout();
}
/**
* Initializes various states for this workspace.
*/
private void initSlidingLayout() {
Context context = getContext();
mScrollInterpolator = new WorkspaceOvershootInterpolator();
mScroller = new Scroller(context, mScrollInterpolator);
mCurrentScreen = mDefaultScreen;
}
/**
* Sets the current screen.
*
* @param currentScreen
*/
public void setCurrentScreen(int currentScreen) {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
scrollTo(mCurrentScreen * getWidth(), 0);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
} else if (mNextScreen != INVALID_SCREEN) {
mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
mNextScreen = INVALID_SCREEN;
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
boolean restore = false;
int restoreCount = 0;
boolean fastDraw = mNextScreen == INVALID_SCREEN;
if (fastDraw) {
drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
} else {
final long drawingTime = getDrawingTime();
final float scrollPos = (float) getScrollX() / getWidth();
final int leftScreen = (int) scrollPos;
final int rightScreen = leftScreen + 1;
if (leftScreen >= 0) {
drawChild(canvas, getChildAt(leftScreen), drawingTime);
}
if (scrollPos != leftScreen && rightScreen < getChildCount()) {
drawChild(canvas, getChildAt(rightScreen), drawingTime);
}
}
if (restore) {
canvas.restoreToCount(restoreCount);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
computeScroll();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException(
"Workspace can only be used in EXACTLY mode.");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException(
"Workspace can only be used in EXACTLY mode.");
}
// The children are given the same width and height as the workspace
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
if (mFirstLayout) {
setHorizontalScrollBarEnabled(false);
scrollTo(mCurrentScreen * width, 0);
setHorizontalScrollBarEnabled(true);
mFirstLayout = false;
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
int nextChildLeft = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
final int childWidth = child.getMeasuredWidth();
child.layout(nextChildLeft, 0, nextChildLeft + childWidth, child
.getMeasuredHeight());
nextChildLeft += childWidth;
}
}
}
public void snapToScreen(int whichScreen) {
whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
mNextScreen = whichScreen;
View focusedChild = getFocusedChild();
if (focusedChild != null && whichScreen != mCurrentScreen
&& focusedChild == getChildAt(mCurrentScreen)) {
focusedChild.clearFocus();
}
final int screenDelta = Math.max(1, Math.abs(whichScreen - mCurrentScreen));
final int newX = whichScreen * getWidth();
final int delta = newX - getScrollX();
int duration = (screenDelta + 1) * 100;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
mScrollInterpolator.disableSettle();
duration += 100;
mScroller.startScroll(getScrollX(), 0, delta, 0, duration);
invalidate();
}
}