/*
* Copyright (c) 2011 Adam Shanks, Daniel Huckaby
*
* 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.noshufou.android.su.widget;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.TextView;
import com.noshufou.android.su.R;
public class PagerHeader extends ViewGroup {
private static final String TAG = "Su.PagerHeader";
private Context mContext;
private int mDisplayedPage = 0;
private static final int LEFT_ZONE = -1;
private static final int MIDDLE_ZONE = 0;
private static final int RIGHT_ZONE = 1;
private int mLeftZoneEdge;
private int mRightZoneEdge;
private boolean mTouchZonesAccurate;
private int mTouchSlopSquare;
private boolean mAlwaysInTapRegion;
private MotionEvent mCurrentDownEvent;
private ShapeDrawable mTabDrawable;
private ShapeDrawable mBottomBar;
private GradientDrawable mShadow;
private GradientDrawable mFadingEdgeLeft;
private GradientDrawable mFadingEdgeRight;
private OnHeaderClickListener mOnHeaderClickListener = null;
private boolean mChangeOnClick = true;
private ColorSet mActiveTextColor;
private ColorSet mInactiveTextColor;
private ColorSet mTabColor;
private int mTabHeight;
private int mTabPadding;
private int mPaddingPush;
private int mFadingEdgeLength;
private int mShadowHeight;
private int mBottomBarHeight;
private boolean mShowTopShadow;
private boolean mShowBottomBar;
private boolean mShowTab;
private static DisplayMetrics mDisplayMetrics;
public interface OnHeaderClickListener {
public void onHeaderClicked(int position);
public void onHeaderSelected(int position);
}
public PagerHeader(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
Resources resources = context.getResources();
mDisplayMetrics = resources.getDisplayMetrics();
// Get attributes from the layout xml
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagerHeader, 0, 0);
mActiveTextColor = new ColorSet(
a.getColor(R.styleable.PagerHeader_activeTextColor, Color.BLACK));
mInactiveTextColor = new ColorSet(
a.getColor(R.styleable.PagerHeader_inactiveTextColor, Color.DKGRAY));
mTabColor = new ColorSet(
a.getColor(R.styleable.PagerHeader_tabColor, mActiveTextColor.getColor()));
mTabHeight = a.getDimensionPixelSize(R.styleable.PagerHeader_tabHeight, dipToPixels(4));
mTabPadding = a.getDimensionPixelSize(R.styleable.PagerHeader_tabPadding, dipToPixels(10));
mPaddingPush = a.getDimensionPixelSize(
R.styleable.PagerHeader_paddingPush, dipToPixels(50));
mFadingEdgeLength = a.getDimensionPixelSize(
R.styleable.PagerHeader_fadingEdgeLength, dipToPixels(30));
mShowTopShadow = a.getBoolean(R.styleable.PagerHeader_showTopShadow, true);
mShowBottomBar = a.getBoolean(R.styleable.PagerHeader_showBottomBar, true);
mShowTab = a.getBoolean(R.styleable.PagerHeader_showTab, true);
ColorSet fadingEdgeColorHint = new ColorSet(0);
if (a.hasValue(R.styleable.PagerHeader_backgroundColor)) {
int backgroundColor = a.getColor(R.styleable.PagerHeader_backgroundColor, 0);
setBackgroundColor(backgroundColor);
fadingEdgeColorHint.setColor(backgroundColor);
} else if (a.hasValue(R.styleable.PagerHeader_fadingEdgeColorHint)) {
fadingEdgeColorHint.setColor(
a.getColor(R.styleable.PagerHeader_fadingEdgeColorHint, 0));
} else {
Log.w(TAG, "Either backgroundColor or fadingEdgeColorHint must be specified to " +
"enable fading edges");
fadingEdgeColorHint.setColor(0x00000000);
}
mTabDrawable = new ShapeDrawable(new RectShape());
mTabDrawable.getPaint().setColor(mTabColor.getColor());
mBottomBar = new ShapeDrawable(new RectShape());
mBottomBar.getPaint().setColor(mTabColor.getColor());
mBottomBarHeight = dipToPixels(2);
mShadow = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
new int[] {0x88000000, 0x00000000});
mShadowHeight = dipToPixels(3);
int[] fadingEdgeGradient = new int[] { fadingEdgeColorHint.getColor(),
fadingEdgeColorHint.getColor(0) };
mFadingEdgeLeft = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT,
fadingEdgeGradient);
mFadingEdgeRight = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT,
fadingEdgeGradient);
final ViewConfiguration config = ViewConfiguration.get(context);
int touchSlop = config.getScaledTouchSlop();
mTouchSlopSquare = touchSlop * touchSlop;
}
public void add(int index, String label) {
TextView textView = new TextView(mContext);
textView.setTextColor(mInactiveTextColor.getColor());
textView.setTextSize(16);
textView.setText(label);
addView(textView);
}
public void setDisplayedPage(int index) {
mDisplayedPage = index;
}
public void setOnHeaderClickListener(OnHeaderClickListener listener) {
mOnHeaderClickListener = listener;
}
public void setChangeOnClick(boolean changeOnClick) {
mChangeOnClick = changeOnClick;
}
public boolean getChangeOnClick() {
return mChangeOnClick;
}
public void setPosition(int position, float positionOffset, int positionOffsetPixels) {
mTouchZonesAccurate = false;
int width = getWidth();
int center = width / 2;
// Move the view at position. This will be the label for the left
// of the two fragments that may be on the screen
if (position >= 0 && position < getChildCount()) {
TextView view = (TextView) getChildAt(position);
int viewWidth = view.getWidth();
int leftMin = 0;
if (position + 1 < getChildCount()) {
int nextViewWidth = getChildAt(position + 1).getWidth();
leftMin = Math.min(0,
center - (nextViewWidth / 2) - mPaddingPush - viewWidth);
}
int leftMax = center - (viewWidth / 2);
int newLeft = map(positionOffset, 1, 0, leftMin, leftMax);
view.layout(newLeft, view.getTop(), newLeft + viewWidth, view.getBottom());
view.setTextColor(Color.rgb(
map(positionOffset, 1, 0, mInactiveTextColor.red, mActiveTextColor.red),
map(positionOffset, 1, 0, mInactiveTextColor.green, mActiveTextColor.green),
map(positionOffset, 1, 0, mInactiveTextColor.blue, mActiveTextColor.blue)));
}
// Move the view at position + 1. This will be the label for the
// right of the two fragments that may be visible on screen
if ((position + 1) < getChildCount()) {
TextView view = (TextView) getChildAt(position + 1);
int viewWidth = view.getWidth();
int prevViewWidth = getChildAt(position).getWidth();
int leftMin = center - (viewWidth / 2);
int leftMax = Math.max(width - viewWidth,
center + (prevViewWidth / 2) + mPaddingPush);
int newLeft = map(positionOffset, 1, 0, leftMin, leftMax);
view.layout(newLeft, view.getTop(), newLeft + viewWidth, view.getBottom());
view.setTextColor(Color.rgb(
map(positionOffset, 1, 0, mActiveTextColor.red, mInactiveTextColor.red),
map(positionOffset, 1, 0, mActiveTextColor.green, mInactiveTextColor.green),
map(positionOffset, 1, 0, mActiveTextColor.blue, mInactiveTextColor.blue)));
}
// Move the view at position - 1. This will be the label for the
// fragment that is off the screen to the left, if it exists
if (position > 0) {
TextView view = (TextView) getChildAt(position - 1);
int plusOneLeft = getChildAt(position).getLeft();
int newLeft = view.getLeft();
int viewWidth = view.getWidth();
if (plusOneLeft < newLeft + viewWidth + mPaddingPush || newLeft < 0) {
newLeft = Math.min(0, plusOneLeft - viewWidth - mPaddingPush);
view.layout(newLeft, view.getTop(), newLeft + viewWidth, view.getBottom());
int alpha = map(positionOffset, 1, 0, 0, 255);
view.setTextColor(mInactiveTextColor.getColor(alpha));
}
}
// Move the view at position + 2. This will be the label for the
// fragment that is off the screen to the right, if it exists
if ((position + 2) < getChildCount()) {
TextView view = (TextView) getChildAt(position + 2);
int minusOneRight = getChildAt(position + 1).getRight();
int newLeft = view.getLeft();
int viewWidth = view.getWidth();
if (minusOneRight > (newLeft - mPaddingPush) || newLeft + viewWidth > width) {
newLeft = Math.max(minusOneRight + mPaddingPush, width - viewWidth);
view.layout(newLeft, view.getTop(), newLeft + viewWidth, view.getBottom());
int alpha = map(positionOffset, 0, 1, 0, 255);
view.setTextColor(mInactiveTextColor.getColor(alpha));
}
}
// Draw the tab under the active or oncoming TextView based on the
// positionOffset
View view = getChildAt(positionOffset < 0.5f?position:position + 1);
int viewLeft = view.getLeft();
int viewRight = view.getRight();
float percent = (Math.abs(positionOffset - 0.5f)/0.5f);
int tabHeight = (int) (mTabHeight * percent);
int alpha = (int) (255 * percent);
mTabDrawable.setBounds(viewLeft - mTabPadding,
getHeight() - tabHeight,
viewRight + mTabPadding,
getHeight());
mTabDrawable.setAlpha(alpha);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int textHeight = 0;
int textWidth = 0;
for (int i = 0; i < getChildCount(); i++ ) {
View view = getChildAt(i);
view.measure(0, 0);
textHeight = Math.max(textHeight, view.getMeasuredHeight());
textWidth = Math.max(textWidth, view.getMeasuredWidth());
}
int desiredWidth = textWidth + (mFadingEdgeLength *2);
int width = resolveSize(desiredWidth, widthMeasureSpec);
int desiredHeight = textHeight + getPaddingTop() + getPaddingBottom()
+ mShadowHeight + mTabHeight;
int height = resolveSize(desiredHeight, heightMeasureSpec);
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int right = r - l;
int height = b - t;
for (int i = 0; i < getChildCount(); i++) {
// Put all the children outside of the view, then use setPosition
// to put the ones that need to be seen back. This will be where
// we set the top and bottom of every view though.
TextView view = (TextView) getChildAt(i);
int viewWidth = view.getMeasuredWidth();
int viewHeight = view.getMeasuredHeight();
int textTop = (height/2) - (viewHeight -
((view.getLineHeight()*view.getLineCount())/2));
view.layout(right,
textTop,
right + viewWidth,
textTop + viewHeight);
}
setPosition(mDisplayedPage, 0, 0);
mShadow.setBounds(0, 0, right, mShadowHeight);
mBottomBar.setBounds(0, height - mBottomBarHeight, right, height);
// Set up the fading edges
mFadingEdgeLeft.setBounds(0, mShadowHeight, mFadingEdgeLength, height - mBottomBarHeight);
mFadingEdgeRight.setBounds(right - mFadingEdgeLength, mShadowHeight,
right, height - mBottomBarHeight);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
mFadingEdgeLeft.draw(canvas);
mFadingEdgeRight.draw(canvas);
if (mShowTopShadow) mShadow.draw(canvas);
if (mShowBottomBar) mBottomBar.draw(canvas);
if (mShowTab) mTabDrawable.draw(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getAction();
final float x = event.getX();
final float y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
mCurrentDownEvent = MotionEvent.obtain(event);
mAlwaysInTapRegion = true;
showPress(mCurrentDownEvent, true);
break;
case MotionEvent.ACTION_MOVE:
if (mAlwaysInTapRegion) {
final int deltaX = (int) (x - mCurrentDownEvent.getX());
final int deltaY = (int) (y - mCurrentDownEvent.getY());
int distance = (deltaX * deltaX) + (deltaY * deltaY);
if (distance > mTouchSlopSquare) {
mAlwaysInTapRegion = false;
showPress(mCurrentDownEvent, false);
}
}
break;
case MotionEvent.ACTION_UP:
showPress(mCurrentDownEvent, false);
if (mAlwaysInTapRegion) {
onTap(mCurrentDownEvent);
}
}
return true;
}
private void showPress(MotionEvent event, boolean pressed) {
int touchZone = getTouchZone(event);
if (touchZone != MIDDLE_ZONE) {
TextView view = (TextView) getChildAt(mDisplayedPage + getTouchZone(event));
view.setTextColor(pressed?mActiveTextColor.getColor():mInactiveTextColor.getColor());
}
}
private void onTap(MotionEvent event) {
int touchZone = getTouchZone(event);
if (mOnHeaderClickListener != null) {
mOnHeaderClickListener.onHeaderClicked(mDisplayedPage + touchZone);
}
if (mChangeOnClick && touchZone != MIDDLE_ZONE) {
setDisplayedPage(mDisplayedPage + touchZone);
if (mOnHeaderClickListener != null) {
mOnHeaderClickListener.onHeaderSelected(mDisplayedPage);
}
}
}
private int getTouchZone(MotionEvent event) {
if (mTouchZonesAccurate) {
int x = (int) event.getX();
if (x < mLeftZoneEdge && mDisplayedPage > 0) {
return LEFT_ZONE;
} else if (x > mLeftZoneEdge && x < mRightZoneEdge) {
return MIDDLE_ZONE;
} else if (x > mRightZoneEdge && mDisplayedPage < getChildCount() -1) {
return RIGHT_ZONE;
}
} else {
View middleChild = getChildAt(mDisplayedPage);
int mLeft = middleChild.getLeft();
int mRight = middleChild.getRight();
View leftChild = getChildAt(mDisplayedPage - 1);
int lRight = leftChild!=null?leftChild.getRight():-1;
View rightChild = getChildAt(mDisplayedPage + 1);
int rLeft = rightChild!=null?rightChild.getLeft():-1;
mLeftZoneEdge = lRight < 0 ? 0 : lRight + ((mLeft - lRight)/2);
mRightZoneEdge = rLeft < 0 ? getWidth() : rLeft - ((rLeft - mRight)/2);
mTouchZonesAccurate = true;
return getTouchZone(event);
}
return MIDDLE_ZONE;
}
private static int map(float value, float fromLow, float fromHigh, int toLow, int toHigh) {
return (int) ((value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow);
}
/**
* Converts density independent pixel value to raw pixel value
*/
private static int dipToPixels(float dipValue) {
return (int) (mDisplayMetrics.density * dipValue + 0.5f);
}
private class ColorSet {
public int alpha;
public int red;
public int green;
public int blue;
ColorSet(int color) {
setColor(color);
}
public void setColor(int color) {
alpha = Color.alpha(color);
red = Color.red(color);
green = Color.green(color);
blue = Color.blue(color);
}
public int getColor() {
return Color.argb(alpha, red, green, blue);
}
public int getColor(int alphaOverride) {
return Color.argb(alphaOverride, red, green, blue);
}
}
}