package nu.mine.tmyymmt.android.widget;
import java.util.ArrayList;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.app.Activity;
import android.content.Context;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
/**
* Retractive Scroll Views for Android.
*
* This is a library for Android. This is a retractable HorizontalScrollView.
* You can also sync scrolling in views.
*
* @version 0.1
* @see https://github.com/tmyymmt/Retractive-Scroll-Views-for-Android
* @author Tomoya Yamamoto <a
* href="mailto:tmyymmt+github@gmail.com"><tmyymmt+
* github@gmail.com></a> <a
* href="http://about.me/tmyymmt">http://about.me/tmyymmt</a>
*/
public class RetractiveHorizontalScrollView extends android.widget.HorizontalScrollView {
/**
* Flag for moving when called OnScrollChanged(). If this flag is true then
* move this view.
*/
private boolean moveOnScrollChangedFlag_ = false;
/**
* Last action which is event of onTouch(View v, MotionEvent event).
*/
private int lastAction_ = 0;
/**
* RetractiveHorizontalScrollViews to sync with this object.
*/
private ArrayList<RetractiveHorizontalScrollView> syncScrollViews_ = null;
/**
* The width of retracting by pixel size. Default value is the absolute
* width of the display in pixels.
*/
private int retractiveWidth_ = 0;
/**
* This is double of retractiveWidth_ by pixel size.
*/
private int retractiveWidthDouble_ = 0;
/**
* Scroll position which is right side border.
*/
private int rightPosition_ = 0;
/**
* Commander object. This is a watchdog for eternal loop.
*/
private RetractiveHorizontalScrollView commanderObject_ = null;
/**
* Constructor.
*
* @param context
* The Context the view is running in, through which it can
* access the current theme, resources, etc.
*/
public RetractiveHorizontalScrollView(Context context) {
super(context);
init();
}
/**
* Constructor.
*
* @param context
* The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param attrs
* The attributes of the XML tag that is inflating the view.
*/
public RetractiveHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/**
* Constructor.
*
* @param context
* The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param attrs
* The attributes of the XML tag that is inflating the view.
* @param defStyle
* The default style to apply to this view. If 0, no style will
* be applied (beyond what is included in the theme). This may
* either be an attribute resource, whose value will be retrieved
* from the current theme, or an explicit style resource.
*/
public RetractiveHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
/**
* Set retractive width by pixel size. Default value is the absolute width
* of the display in pixels.
*
* @param retractiveWidth
* retractive width by pixel size.
*/
public void setRetractiveWidth(int retractiveWidth) {
retractiveWidth_ = retractiveWidth;
retractiveWidthDouble_ = retractiveWidth_ * 2;
updateRetractiveWidth();
}
/**
* Return retractive width by pixel size.
*
* @return retractive width by pixel size
*/
public int getRetractiveWidth() {
return retractiveWidth_;
}
/**
* Return sync views.
*
* @return sync views
*/
public ArrayList<RetractiveHorizontalScrollView> getSyncScrollViews() {
return syncScrollViews_;
}
/**
* Add sync view.
*
* @param syncScrollView
* sync views.
*/
public void addSyncScrollView(RetractiveHorizontalScrollView syncScrollView) {
if (syncScrollView != null && syncScrollView != this) {
syncScrollViews_.add(syncScrollView);
}
}
/**
* Remove sync view.
*
* @param syncScrollView
* sync views.
*/
public void removeSyncScrollView(RetractiveHorizontalScrollView syncScrollView) {
syncScrollViews_.remove(syncScrollView);
}
/**
* Clear sync view.
*/
public void clearSyncScrollView() {
syncScrollViews_.clear();
}
/**
* Return the flag.
*
* @return true: move when this is on scroll changed. false: don't move when
* this is on scroll changed.
*/
public boolean isMoveOnScrollChanged() {
return moveOnScrollChangedFlag_;
}
/**
* Set the flag.
*
* @param move
* true: move when this is on scroll changed. false: don't move
* when this is on scroll changed.
*/
public void setMoveOnScrollChanged(boolean move) {
this.moveOnScrollChangedFlag_ = move;
}
/**
* @see android.widget.HorizontalScrollView#onLayout(boolean, int, int, int,
* int)
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
init2();
}
/**
* @see android.view.View#onScrollChanged(int, int, int, int)
*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (commanderObject_ == this) {
commanderObject_ = null;
} else {
for (RetractiveHorizontalScrollView syncView : syncScrollViews_) {
commanderObject_ = this;
syncView.commanderObject_ = this;
syncView.scrollTo(l, t);
}
}
if (moveOnScrollChangedFlag_) {
if (rightPosition_ < getScrollX() && retractiveWidth_ < rightPosition_) {
scrollTo(rightPosition_, getScrollY());
} else if (getScrollX() < retractiveWidth_) {
scrollTo(retractiveWidth_, getScrollY());
}
}
}
/**
* @see android.widget.HorizontalScrollView#computeHorizontalScrollRange()
*/
@Override
protected int computeHorizontalScrollRange() {
int count = getChildCount();
if (count == 0)
return getWidth();
return getChildAt(0).getRight() - retractiveWidthDouble_;
}
/**
* @see android.widget.HorizontalScrollView#computeHorizontalScrollOffset()
*/
@Override
protected int computeHorizontalScrollOffset() {
if (getScrollX() < retractiveWidth_)
return 0;
return getScrollX() - retractiveWidth_;
}
/**
* @see android.view.View#computeHorizontalScrollExtent()
*/
@Override
protected int computeHorizontalScrollExtent() {
if (rightPosition_ < getScrollX()) {
return Math.max(1, getWidth() - (getScrollX() - rightPosition_));
} else if (getScrollX() < retractiveWidth_) {
return Math.max(1, getWidth() - (retractiveWidth_ - getScrollX()));
}
return getWidth();
}
/**
* @see android.widget.HorizontalScrollView#fullScroll(int)
*/
@Override
public boolean fullScroll(int direction) {
if (((LinearLayout) getChildAt(0)).getChildAt(1).getWidth() < getWidth()
&& direction == HorizontalScrollView.FOCUS_RIGHT)
return false;
return super.fullScroll(direction);
}
/**
* @see android.widget.HorizontalScrollView#pageScroll(int)
*/
@Override
public boolean pageScroll(int direction) {
if (((LinearLayout) getChildAt(0)).getChildAt(1).getWidth() < getWidth()
&& direction == HorizontalScrollView.FOCUS_RIGHT)
return false;
return super.pageScroll(direction);
}
/**
* Initialization.
*/
private void init() {
syncScrollViews_ = new ArrayList<RetractiveHorizontalScrollView>();
if (getContext() instanceof Activity && retractiveWidth_ == 0) {
DisplayMetrics metrics = new DisplayMetrics();
((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics);
setRetractiveWidth(metrics.widthPixels);
}
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
moveOnScrollChangedFlag_ = true;
for (RetractiveHorizontalScrollView syncView : syncScrollViews_) {
syncView.moveOnScrollChangedFlag_ = true;
}
if (rightPosition_ < getScrollX()) {
scrollTo(rightPosition_, 0);
} else if (getScrollX() < retractiveWidth_) {
scrollTo(retractiveWidth_, 0);
}
} else if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) {
moveOnScrollChangedFlag_ = false;
for (RetractiveHorizontalScrollView syncView : syncScrollViews_) {
syncView.moveOnScrollChangedFlag_ = false;
}
}
if (getLastAction() == MotionEvent.ACTION_UP && event.getAction() == MotionEvent.ACTION_MOVE) {
event.setAction(MotionEvent.ACTION_DOWN);
}
setLastAction(event.getAction());
return false;
}
});
}
/**
* Initialization 2. This is after GUI was inflate.
*/
private void init2() {
updateRetractiveWidth();
rightPosition_ = getChildAt(0).getRight() - retractiveWidth_ - getWidth();
if (((LinearLayout) getChildAt(0)).getChildAt(1).getWidth() < getWidth()) {
scrollTo(retractiveWidth_, getScrollY());
for (RetractiveHorizontalScrollView syncView : syncScrollViews_) {
syncView.scrollTo(retractiveWidth_, syncView.getScrollY());
}
} else {
moveOnScrollChangedFlag_ = true;
for (RetractiveHorizontalScrollView syncView : syncScrollViews_) {
syncView.moveOnScrollChangedFlag_ = true;
}
scrollTo(0, getScrollY());
fullScroll(HorizontalScrollView.FOCUS_RIGHT);
for (RetractiveHorizontalScrollView syncView : syncScrollViews_) {
syncView.scrollTo(0, syncView.getScrollY());
syncView.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
}
}
}
/**
* Update retractive width.
*/
private void updateRetractiveWidth() {
if (getChildAt(0) != null) {
((LinearLayout) getChildAt(0)).getChildAt(0).setMinimumWidth(retractiveWidth_);
((LinearLayout) getChildAt(0)).getChildAt(2).setMinimumWidth(retractiveWidth_);
}
}
/**
* Return last action. Last action which is event of onTouch(View v,
* MotionEvent event).
*
* @return last action
*/
private int getLastAction() {
return lastAction_;
}
/**
* Set last action. Last action which is event of onTouch(View v,
* MotionEvent event).
*
* @param lastAction
* last action.
*/
private void setLastAction(int lastAction) {
this.lastAction_ = lastAction;
}
}