我有兴趣创建一个“捕捉”到所查看项目的水平滚动视图,因此一次只显示一个项目。用户可以左/右触摸拖动并看到上一个/下一个视图,如果有足够的速度则切换到它。这种互动就像Nexus One附带的新天气/新闻小部件在其“标签”之间导航一样。
是否有任何现有的视图窗口小部件可以执行此操作?
更新:找到了新闻/天气小部件(GenieWidget)的副本,他们似乎已经实现了自己的小部件来实现这一点,他们称之为com.google.android.apps.genie.geniewidget.ui.FlingableLinearLayout
,这是他们自己的自定义com.google.android.apps.genie.geniewidget.ui.TabView
的一部分。由于这个来源不可用,所以看起来并不太有希望。
答案 0 :(得分:12)
(更新20110905:官方android tools现在做得更好)
我在github上克隆了Eric Taix的http://code.google.com/p/andro-views/
https://github.com/olibye/AndroViews
然后应用上面的补丁:
JonO的补丁
Tom de Waard的补丁
分成一个库和一个例子,允许简单地包含在其他项目中
我会在上面发表这个评论,但我似乎无法评论JonO的回答
答案 1 :(得分:5)
不要看新闻和天气实施,它有一些缺陷。但是,您可以在android.git.kernel.org上使用Home应用程序的源代码(称为Launcher或Launcher2)。我们用于在Home上滚动的小部件位于Workspace.java。
答案 2 :(得分:4)
Eric Taix完成了将Workspace剥离到可以重用的WorkspaceView的大部分工作。可在此处找到:http://code.google.com/p/andro-views/
发布时的版本会在模拟器中实现它应有的功能,但在真实硬件上它有时会卡在视图之间而不是回弹 - 我已经通过电子邮件向他发送了一个补丁(他在提交之前正在测试,发布此日期的日期应该使其行为与工作区完全一样。
如果补丁没有在那里出现,我会单独发布。
正如所承诺的,因为它还没有出现,这是我的修补版本:
/**
* Copyright 2010 Eric Taix (eric.taix@gmail.com) 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.
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.Interpolator;
import android.widget.Scroller;
/**
* The workspace is a wide area with a infinite number of screens. Each screen contains a view. A workspace is meant to
* be used with a fixed width only.<br/>
* <br/>
* This code has been done by using com.android.launcher.Workspace.java
*/
public class WorkspaceView extends ViewGroup {
private static final int INVALID_POINTER = -1;
private int mActivePointerId = INVALID_POINTER;
private static final int INVALID_SCREEN = -1;
// The velocity at which a fling gesture will cause us to snap to the next screen
private static final int SNAP_VELOCITY = 500;
// the default screen index
private int defaultScreen;
// The current screen index
private int currentScreen;
// The next screen index
private int nextScreen = INVALID_SCREEN;
// Wallpaper properties
private Bitmap wallpaper;
private Paint paint;
private int wallpaperWidth;
private int wallpaperHeight;
private float wallpaperOffset;
private boolean wallpaperLoaded;
private boolean firstWallpaperLayout = true;
private static final int TAB_INDICATOR_HEIGHT_PCT = 2;
private RectF selectedTab;
// The scroller which scroll each view
private Scroller scroller;
// A tracker which to calculate the velocity of a mouvement
private VelocityTracker mVelocityTracker;
// Tha last known values of X and Y
private float lastMotionX;
private float lastMotionY;
private final static int TOUCH_STATE_REST = 0;
private final static int TOUCH_STATE_SCROLLING = 1;
// The current touch state
private int touchState = TOUCH_STATE_REST;
// The minimal distance of a touch slop
private int touchSlop;
// An internal flag to reset long press when user is scrolling
private boolean allowLongPress;
// A flag to know if touch event have to be ignored. Used also in internal
private boolean locked;
private WorkspaceOvershootInterpolator mScrollInterpolator;
private int mMaximumVelocity;
private Paint selectedTabPaint;
private Canvas canvas;
private RectF bar;
private Paint tabIndicatorBackgroundPaint;
private static class WorkspaceOvershootInterpolator implements Interpolator {
private static final float DEFAULT_TENSION = 1.3f;
private float mTension;
public WorkspaceOvershootInterpolator() {
mTension = DEFAULT_TENSION;
}
public void setDistance(int distance) {
mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
}
public void disableSettle() {
mTension = 0.f;
}
public float getInterpolation(float t) {
// _o(t) = t * t * ((tension + 1) * t + tension)
// o(t) = _o(t - 1) + 1
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 WorkspaceView(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 WorkspaceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
defaultScreen = 0;
initWorkspace();
}
/**
* Initializes various states for this workspace.
*/
private void initWorkspace() {
mScrollInterpolator = new WorkspaceOvershootInterpolator();
scroller = new Scroller(getContext(),mScrollInterpolator);
currentScreen = defaultScreen;
paint = new Paint();
paint.setDither(false);
// Does this do anything for me?
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
touchSlop = configuration.getScaledTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
selectedTabPaint = new Paint();
selectedTabPaint.setColor(getResources().getColor(R.color.RED));
selectedTabPaint.setStyle(Paint.Style.FILL_AND_STROKE);
tabIndicatorBackgroundPaint = new Paint();
tabIndicatorBackgroundPaint.setColor(getResources().getColor(R.color.GRAY));
tabIndicatorBackgroundPaint.setStyle(Paint.Style.FILL);
}
/**
* Set a new distance that a touch can wander before we think the user is scrolling in pixels slop<br/>
*
* @param touchSlopP
*/
public void setTouchSlop(int touchSlopP) {
touchSlop = touchSlopP;
}
/**
* Set the background's wallpaper.
*/
public void loadWallpaper(Bitmap bitmap) {
wallpaper = bitmap;
wallpaperLoaded = true;
requestLayout();
invalidate();
}
boolean isDefaultScreenShowing() {
return currentScreen == defaultScreen;
}
/**
* Returns the index of the currently displayed screen.
*
* @return The index of the currently displayed screen.
*/
int getCurrentScreen() {
return currentScreen;
}
/**
* Sets the current screen.
*
* @param currentScreen
*/
public void setCurrentScreen(int currentScreen) {
if (!scroller.isFinished()) scroller.abortAnimation();
currentScreen = Math.max(0, Math.min(currentScreen, getChildCount()));
scrollTo(currentScreen * getWidth(), 0);
Log.d("workspace", "setCurrentScreen: width is " + getWidth());
invalidate();
}
/**
* Shows the default screen (defined by the firstScreen attribute in XML.)
*/
void showDefaultScreen() {
setCurrentScreen(defaultScreen);
}
/**
* Registers the specified listener on each screen contained in this workspace.
*
* @param l The listener used to respond to long clicks.
*/
@Override
public void setOnLongClickListener(OnLongClickListener l) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).setOnLongClickListener(l);
}
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate();
} else if (nextScreen != INVALID_SCREEN) {
currentScreen = Math.max(0, Math.min(nextScreen, getChildCount() - 1));
nextScreen = INVALID_SCREEN;
}
}
/**
* ViewGroup.dispatchDraw() supports many features we don't need: clip to padding, layout animation, animation
* listener, disappearing children, etc. The following implementation attempts to fast-track the drawing dispatch by
* drawing only what we know needs to be drawn.
*/
@Override
protected void dispatchDraw(Canvas canvas) {
// First draw the wallpaper if needed
if (wallpaper != null) {
float x = getScrollX() * wallpaperOffset;
if (x + wallpaperWidth < getRight() - getLeft()) {
x = getRight() - getLeft() - wallpaperWidth;
}
canvas.drawBitmap(wallpaper, x, (getBottom() - getTop() - wallpaperHeight) / 2, paint);
}
// Determine if we need to draw every child or only the current screen
boolean fastDraw = touchState != TOUCH_STATE_SCROLLING && nextScreen == INVALID_SCREEN;
// If we are not scrolling or flinging, draw only the current screen
if (fastDraw) {
View v = getChildAt(currentScreen);
drawChild(canvas, v, getDrawingTime());
}
else {
final long drawingTime = getDrawingTime();
// If we are flinging, draw only the current screen and the target screen
if (nextScreen >= 0 && nextScreen < getChildCount() && Math.abs(currentScreen - nextScreen) == 1) {
drawChild(canvas, getChildAt(currentScreen), drawingTime);
drawChild(canvas, getChildAt(nextScreen), drawingTime);
}
else {
// If we are scrolling, draw all of our children
final int count = getChildCount();
for (int i = 0; i < count; i++) {
drawChild(canvas, getChildAt(i), drawingTime);
}
}
}
updateTabIndicator();
canvas.drawBitmap(bitmap, getScrollX(), getMeasuredHeight()*(100-TAB_INDICATOR_HEIGHT_PCT)/100, paint);
}
/**
* Measure the workspace AND also children
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int height = MeasureSpec.getSize(heightMeasureSpec);
// Log.d("workspace","Height is " + height);
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++) {
int adjustedHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height*(100-TAB_INDICATOR_HEIGHT_PCT)/100, heightMode);
getChildAt(i).measure(widthMeasureSpec,adjustedHeightMeasureSpec);
}
// Compute wallpaper
if (wallpaperLoaded) {
wallpaperLoaded = false;
wallpaper = centerToFit(wallpaper, width, height, getContext());
wallpaperWidth = wallpaper.getWidth();
wallpaperHeight = wallpaper.getHeight();
}
wallpaperOffset = wallpaperWidth > width ? (count * width - wallpaperWidth) / ((count - 1) * (float) width) : 1.0f;
if (firstWallpaperLayout) {
scrollTo(currentScreen * width, 0);
firstWallpaperLayout = false;
}
// Log.d("workspace","Top is "+getTop()+", bottom is "+getBottom()+", left is "+getLeft()+", right is "+getRight());
updateTabIndicator();
invalidate();
}
Bitmap bitmap;
private OnLoadListener load;
private int lastEvHashCode;
private void updateTabIndicator(){
int width = getMeasuredWidth();
int height = getMeasuredHeight();
//For drawing in its own bitmap:
bar = new RectF(0, 0, width, (TAB_INDICATOR_HEIGHT_PCT*height/100));
int startPos = getScrollX()/(getChildCount());
selectedTab = new RectF(startPos, 0, startPos+width/getChildCount(), (TAB_INDICATOR_HEIGHT_PCT*height/100));
bitmap = Bitmap.createBitmap(width, (TAB_INDICATOR_HEIGHT_PCT*height/100), Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
canvas.drawRoundRect(bar,0,0, tabIndicatorBackgroundPaint);
canvas.drawRoundRect(selectedTab, 5,5, selectedTabPaint);
}
/**
* Overrided method to layout child
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int childLeft = 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(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
childLeft += childWidth;
}
}
load.onLoad();
}
@Override
public boolean dispatchUnhandledMove(View focused, int direction) {
if (direction == View.FOCUS_LEFT) {
if (getCurrentScreen() > 0) {
scrollToScreen(getCurrentScreen() - 1);
return true;
}
}
else if (direction == View.FOCUS_RIGHT) {
if (getCurrentScreen() < getChildCount() - 1) {
scrollToScreen(getCurrentScreen() + 1);
return true;
}
}
return super.dispatchUnhandledMove(focused, direction);
}
/**
* This method JUST determines whether we want to intercept the motion. If we return true, onTouchEvent will be called
* and we do the actual scrolling there.
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("workspace","Intercepted a touch event");
if (locked) {
return true;
}
/*
* Shortcut the most recurring case: the user is in the dragging state and he is moving his finger. We want to
* intercept this motion.
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (touchState != TOUCH_STATE_REST)) {
return true;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
// switch (action & MotionEvent.ACTION_MASK) {
switch (action) {
case MotionEvent.ACTION_MOVE:
// Log.d("workspace","Intercepted a move event");
/*
* Locally do absolute value. mLastMotionX is set to the y value of the down event.
*/
handleInterceptMove(ev);
break;
case MotionEvent.ACTION_DOWN:
// Remember location of down touch
final float x1 = ev.getX();
final float y1 = ev.getY();
lastMotionX = x1;
lastMotionY = y1;
allowLongPress = true;
mActivePointerId = ev.getPointerId(0);
/*
* If being flinged and user touches the screen, initiate drag; otherwise don't. mScroller.isFinished should be
* false when being flinged.
*/
touchState = scroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mActivePointerId = INVALID_POINTER;
allowLongPress = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
touchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
}
/*
* The only time we want to intercept motion events is if we are in the drag mode.
*/
return touchState != TOUCH_STATE_REST;
}
private void handleInterceptMove(MotionEvent ev) {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
final int xDiff = (int) Math.abs(x - lastMotionX);
final int yDiff = (int) Math.abs(y - lastMotionY);
boolean xMoved = xDiff > touchSlop;
boolean yMoved = yDiff > touchSlop;
if (xMoved || yMoved) {
//Log.d("workspace","Detected move. Checking to scroll.");
if (xMoved && !yMoved) {
//Log.d("workspace","Detected X move. Scrolling.");
// Scroll if the user moved far enough along the X axis
touchState = TOUCH_STATE_SCROLLING;
lastMotionX = x;
}
// Either way, cancel any pending longpress
if (allowLongPress) {
allowLongPress = false;
// Try canceling the long press. It could also have been scheduled
// by a distant descendant, so use the mAllowLongPress flag to block
// everything
final View currentView = getChildAt(currentScreen);
currentView.cancelLongPress();
}
}
}
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >>
MotionEvent.ACTION_POINTER_ID_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
lastMotionX = ev.getX(newPointerIndex);
lastMotionY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
}
}
/**
* Track the touch event
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
// Log.d("workspace","caught a touch event");
if (locked) {
return true;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
final float x = ev.getX();
switch (action) {
case MotionEvent.ACTION_DOWN:
//We can still get here even if we returned false from the intercept function.
//That's the only way we can get a TOUCH_STATE_REST (0) here.
//That means that our child hasn't handled the event, so we need to
// Log.d("workspace","caught a down touch event and touchstate =" + touchState);
if(touchState != TOUCH_STATE_REST){
/*
* If being flinged and user touches, stop the fling. isFinished will be false if being flinged.
*/
if (!scroller.isFinished()) {
scroller.abortAnimation();
}
// Remember where the motion event started
lastMotionX = x;
mActivePointerId = ev.getPointerId(0);
}
break;
case MotionEvent.ACTION_MOVE:
if (touchState == TOUCH_STATE_SCROLLING) {
handleScrollMove(ev);
} else {
// Log.d("workspace","caught a move touch event but not scrolling");
//NOTE: We will never hit this case in Android 2.2. This is to fix a 2.1 bug.
//We need to do the work of interceptTouchEvent here because we don't intercept the move
//on children who don't scroll.
Log.d("workspace","handling move from onTouch");
if(onInterceptTouchEvent(ev) && touchState == TOUCH_STATE_SCROLLING){
handleScrollMove(ev);
}
}
break;
case MotionEvent.ACTION_UP:
// Log.d("workspace","caught an up touch event");
if (touchState == TOUCH_STATE_SCROLLING) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocityX = (int) velocityTracker.getXVelocity();
if (velocityX > SNAP_VELOCITY && currentScreen > 0) {
// Fling hard enough to move left
scrollToScreen(currentScreen - 1);
}
else if (velocityX < -SNAP_VELOCITY && currentScreen < getChildCount() - 1) {
// Fling hard enough to move right
scrollToScreen(currentScreen + 1);
}
else {
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
touchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
break;
case MotionEvent.ACTION_CANCEL:
Log.d("workspace","caught a cancel touch event");
touchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
break;
case MotionEvent.ACTION_POINTER_UP:
Log.d("workspace","caught a pointer up touch event");
onSecondaryPointerUp(ev);
break;
}
return true;
}
private void handleScrollMove(MotionEvent ev){
// Scroll to follow the motion event
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x1 = ev.getX(pointerIndex);
final int deltaX = (int) (lastMotionX - x1);
lastMotionX = x1;
if (deltaX < 0) {
if (getScrollX() > 0) {
//Scrollby invalidates automatically
scrollBy(Math.max(-getScrollX(), deltaX), 0);
}
}
else if (deltaX > 0) {
final int availableToScroll = getChildAt(getChildCount() - 1).getRight() - getScrollX() - getWidth();
if (availableToScroll > 0) {
//Scrollby invalidates automatically
scrollBy(Math.min(availableToScroll, deltaX), 0);
}
} else {
awakenScrollBars();
}
}
/**
* Scroll to the appropriated screen depending of the current position
*/
private void snapToDestination() {
final int screenWidth = getWidth();
final int whichScreen = (getScrollX() + (screenWidth / 2)) / screenWidth;
Log.d("workspace", "snapToDestination");
scrollToScreen(whichScreen);
}
/**
* Scroll to a specific screen
*
* @param whichScreen
*/
public void scrollToScreen(int whichScreen) {
scrollToScreen(whichScreen, false);
}
private void scrollToScreen(int whichScreen, boolean immediate){
Log.d("workspace", "snapToScreen=" + whichScreen);
boolean changingScreens = whichScreen != currentScreen;
nextScreen = whichScreen;
View focusedChild = getFocusedChild();
if (focusedChild != null && changingScreens && focusedChild == getChildAt(currentScreen)) {
focusedChild.clearFocus();
}
final int newX = whichScreen * getWidth();
final int delta = newX - getScrollX();
Log.d("workspace", "newX=" + newX + " scrollX=" + getScrollX() + " delta=" + delta);
scroller.startScroll(getScrollX(), 0, delta, 0, immediate ? 0 : Math.abs(delta) * 2);
invalidate();
}
public void scrollToScreenImmediate(int whichScreen){
scrollToScreen(whichScreen, true);
}
/**
* Return the parceable instance to be saved
*/
@Override
protected Parcelable onSaveInstanceState() {
final SavedState state = new SavedState(super.onSaveInstanceState());
state.currentScreen = currentScreen;
return state;
}
/**
* Restore the previous saved current screen
*/
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
if (savedState.currentScreen != -1) {
currentScreen = savedState.currentScreen;
}
}
/**
* Scroll to the left right screen
*/
public void scrollLeft() {
if (nextScreen == INVALID_SCREEN && currentScreen > 0 && scroller.isFinished()) {
scrollToScreen(currentScreen - 1);
}
}
/**
* Scroll to the next right screen
*/
public void scrollRight() {
if (nextScreen == INVALID_SCREEN && currentScreen < getChildCount() - 1 && scroller.isFinished()) {
scrollToScreen(currentScreen + 1);
}
}
/**
* Return the screen's index where a view has been added to.
*
* @param v
* @return
*/
public int getScreenForView(View v) {
int result = -1;
if (v != null) {
ViewParent vp = v.getParent();
int count = getChildCount();
for (int i = 0; i < count; i++) {
if (vp == getChildAt(i)) {
return i;
}
}
}
return result;
}
/**
* Return a view instance according to the tag parameter or null if the view could not be found
*
* @param tag
* @return
*/
public View getViewForTag(Object tag) {
int screenCount = getChildCount();
for (int screen = 0; screen < screenCount; screen++) {
View child = getChildAt(screen);
if (child.getTag() == tag) {
return child;
}
}
return null;
}
/**
* Unlocks the SlidingDrawer so that touch events are processed.
*
* @see #lock()
*/
public void unlock() {
locked = false;
}
/**
* Locks the SlidingDrawer so that touch events are ignores.
*
* @see #unlock()
*/
public void lock() {
locked = true;
}
/**
* @return True is long presses are still allowed for the current touch
*/
public boolean allowLongPress() {
return allowLongPress;
}
/**
* Move to the default screen
*/
public void moveToDefaultScreen() {
scrollToScreen(defaultScreen);
getChildAt(defaultScreen).requestFocus();
}
// ========================= INNER CLASSES ==============================
/**
* A SavedState which save and load the current screen
*/
public static class SavedState extends BaseSavedState {
int currentScreen = -1;
/**
* Internal constructor
*
* @param superState
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Private constructor
*
* @param in
*/
private SavedState(Parcel in) {
super(in);
currentScreen = in.readInt();
}
/**
* Save the current screen
*/
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(currentScreen);
}
/**
* Return a Parcelable creator
*/
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
//Added for "flipper" compatibility
public int getDisplayedChild(){
return getCurrentScreen();
}
public void setDisplayedChild(int i){
// setCurrentScreen(i);
scrollToScreen(i);
getChildAt(i).requestFocus();
}
public void setOnLoadListener(OnLoadListener load){
this.load = load;
}
public void flipLeft(){
scrollLeft();
}
public void flipRight(){
scrollRight();
}
// ======================== UTILITIES METHODS ==========================
/**
* Return a centered Bitmap
*
* @param bitmap
* @param width
* @param height
* @param context
* @return
*/
static Bitmap centerToFit(Bitmap bitmap, int width, int height, Context context) {
final int bitmapWidth = bitmap.getWidth();
final int bitmapHeight = bitmap.getHeight();
if (bitmapWidth < width || bitmapHeight < height) {
// Normally should get the window_background color of the context
int color = Integer.valueOf("FF191919", 16);
Bitmap centered = Bitmap.createBitmap(bitmapWidth < width ? width : bitmapWidth, bitmapHeight < height ? height
: bitmapHeight, Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(centered);
canvas.drawColor(color);
canvas.drawBitmap(bitmap, (width - bitmapWidth) / 2.0f, (height - bitmapHeight) / 2.0f, null);
bitmap = centered;
}
return bitmap;
}
}
答案 3 :(得分:0)
我不知道Nexus,但我可以向你推荐画廊视图。根据您的上述解释,它非常适合您的要求。
答案 4 :(得分:0)
答案 5 :(得分:0)
我尝试了许多我们可以在网上找到的代码示例,即使几乎所有代码都很好,但他们并没有正确处理我的想法。
实际上我并排了几个ScrollView
,屏幕只显示一个。当用户垂直滚动时,我希望ScrollView
滚动;当用户水平滚动时,我希望ViewFlow
(我正在使用的全局布局)捕捉到上一个/下一个ScrollView
。
我得到的东西几乎接近我想要的东西,但现在这样做。我知道很久以前就回答了这个问题,但我想分享一下我所做的,所以就是这样。
这只是一个派生ScrollView
并实现onInterceptTouchEvent
和onTouchEvent
的类。
/**
* Try to know if the move will be an horizontal drag
* If so, we want to intercept the touch event (return true)
* otherwise, we don't want to intercept this event
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction() & MotionEvent.ACTION_MASK;
// If scrolling (over X or Y), we want to intercept: process
// it within the ScrollView & send it to the ViewFlow
if (action == MotionEvent.ACTION_MOVE && mState != NO_SCROLL) {
return true;
}
// Try to detect the motion
switch (action) {
case MotionEvent.ACTION_MOVE:
float deltaX = Math.abs(ev.getX()-lastX);
float deltaY = Math.abs(ev.getY()-lastY);
boolean xMoved = deltaX > 0.5* mTouchSlop && deltaX > deltaY;
boolean yMoved = deltaY > 0.5*mTouchSlop;
if (xMoved || yMoved) {
if (xMoved && !yMoved) {
mState = SCROLL_X;
} else {
mState = SCROLL_Y;
}
} else {
mState = NO_SCROLL;
}
lastX = ev.getX();
lastY = ev.getY();
break;
case MotionEvent.ACTION_DOWN:
// Remember location of down touch
lastX = ev.getX();
lastY = ev.getY();
mState = NO_SCROLL;
sendTouchEvent(ev);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mState == NO_SCROLL) {
// Thats a tap!
}
mState = NO_SCROLL;
break;
}
if (mState != SCROLL_X) {
super.onInterceptTouchEvent(ev);
}
return mState == SCROLL_X;// Intercept only dragging over X axis
}
/**
* Handles touch events. Basically only horizontal drag.
* Such events are handled locally by the scrollview
* _AND_ sent to the {@link ViewFlow} in order to make it snap
* horizontally
* @param ev the MotionEvent
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
super.onTouchEvent(ev);
switch (action) {
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mState = NO_SCROLL;
sendTouchEvent(ev);
super.onTouchEvent(ev);
break;
case MotionEvent.ACTION_DOWN:
super.onTouchEvent(ev);
break;
case MotionEvent.ACTION_MOVE:
lastY = ev.getX();
lastY = ev.getY();
if (mState == SCROLL_X) {
sendTouchEvent(ev);
} else if (mState == SCROLL_Y) {
super.onTouchEvent(ev);
}
break;
}
return false;
}
代码并不完美,需要更多的润色,但它的功能。几乎像Genie Widget。 (我希望这个人是开源的!)特别是在滚动检测周围,它并没有完全使用触摸斜率,而是在比较X和Y轴之间的移动。但这很容易调整。
我正在为ViewFlow
:https://github.com/pakerfeldt/android-viewflow