Help with Android UI ListView problems - android

To understand this question, first read how this method works.
I am trying to implements a drag and drop ListView, it's going alright but have run into
a road block. So I don't have to handled everything, I am intercepting(but returning false) MotionEvents sent to the ListView, letting it handle scrolling and stuff. When I want to start dragging a item, I then return true and handled all the dragging stuff. Everything is working fine except for one thing. The drag(drag and drop) is started when it is determined that a long press as a occurred(in onInterceptTouchEvent). I get the Bitmap for the image that I drag around like so. itemPositition being the index of the item that was selected.
(omitting irrelevant parts)
...
View dragItem = mListView.getChildAt(itemPosition);
dragItem.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(dragItem.getDrawingCache());
mDragImage = new ImageView(mContext);
mDragImage.setImageBitmap(bitmap);
...
The problem is, mDragImage is a solid black like this.
But, if I don't let ListView handle anything. As in, I start the drag on ACTION_DOWN and stop on ACTION_UP, mDragImage looks has expected(but I obviously lose scrolling abilities).
Since the drag is started with a long press, the ListView is given the opportunity to do things before the long press occurs. This is my guess as to why this is happening. When a item is pressed, it is highlighted by the ListView. Somewhere in doing so, it is messing with the bitmap. So when I go to get it, it's in a weird state(all black).
I see two options for fixing this, neither of which I know how to do.
Create a image from scratch.
Handle the highlighting myself(if that is the problem).
Option two seems a better one to me, except that I looked at the documentation and the source code and could not find out how to do so. Here are some things I have done/tried.
I set setOnItemClickListener(...) and
setOnItemSelectedListener(...) with a empty method(highlighting
still happens). (Before anyone suggests it, calling
setOnClickListener results in a runtime error.)
I also looked into trying to get the ListView to make a new item
(for option 2), but could not find a way.
Spent 45ish minutes looking through the source code and
documentation trying to pinpoint where the highlighting was
happening(I never found it).
Any help fixing this would be appreciated.
(EDIT1 START)
So I don't actually know if onLongClickListener is working, I made an error before thinking it was. I am trying to set it up right now, will update when I find out if it does.
(EDIT1 END)
Last minute edit before post. I tried using onLongClickListener just now, and the image is good. I would still like to know if there is another way. How I have to use onLongClickListener to get things working is ugly, but it works. I also spent so much time trying to figure this out, it would be nice to find out the answer. I still want to be able to change/handle the highlight color, the default orangeish color is not pretty. Oh and sorry about the length of the post. I could not think of way of making it shorter, while supplying all the information I thought was needed.

use this code, it's allows operation drug and drop in ListView:
public class DraggableListView extends ListView {
private static final String LOG_TAG = "tasks365";
private static final int END_OF_LIST_POSITION = -2;
private DropListener mDropListener;
private int draggingItemHoverPosition;
private int dragStartPosition; // where was the dragged item originally
private int mUpperBound; // scroll the view when dragging point is moving out of this bound
private int mLowerBound; // scroll the view when dragging point is moving out of this bound
private int touchSlop;
private Dragging dragging;
private GestureDetector longPressDetector;
public DraggableListView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.listViewStyle);
}
public DraggableListView(final Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
longPressDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() {
#Override
public void onLongPress(final MotionEvent e) {
int x = (int) e.getX();
final int y = (int) e.getY();
int itemnum = pointToPosition(x, y);
if (itemnum == AdapterView.INVALID_POSITION) {
return;
}
if (dragging != null) {
dragging.stop();
dragging = null;
}
final View item = getChildAt(itemnum - getFirstVisiblePosition());
item.setPressed(false);
dragging = new Dragging(getContext());
dragging.start(y, ((int) e.getRawY()) - y, item);
draggingItemHoverPosition = itemnum;
dragStartPosition = draggingItemHoverPosition;
int height = getHeight();
mUpperBound = Math.min(y - touchSlop, height / 3);
mLowerBound = Math.max(y + touchSlop, height * 2 / 3);
}
});
setOnItemLongClickListener(new OnItemLongClickListener() {
#SuppressWarnings("unused")
public boolean onItemLongClick(AdapterView<?> paramAdapterView, View paramView, int paramInt, long paramLong) {
// Return true to let AbsListView reset touch mode
// Without this handler, the pressed item will keep highlight.
return true;
}
});
}
/* pointToPosition() doesn't consider invisible views, but we need to, so implement a slightly different version. */
private int myPointToPosition(int x, int y) {
if (y < 0) {
return getFirstVisiblePosition();
}
Rect frame = new Rect();
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
child.getHitRect(frame);
if (frame.contains(x, y)) {
return getFirstVisiblePosition() + i;
}
}
if ((x >= frame.left) && (x < frame.right) && (y >= frame.bottom)) {
return END_OF_LIST_POSITION;
}
return INVALID_POSITION;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (longPressDetector.onTouchEvent(ev)) {
return true;
}
if ((dragging == null) || (mDropListener == null)) {
// it is not dragging, or there is no drop listener
return super.onTouchEvent(ev);
}
int action = ev.getAction();
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
dragging.stop();
dragging = null;
if (mDropListener != null) {
if (draggingItemHoverPosition == END_OF_LIST_POSITION) {
mDropListener.drop(dragStartPosition, getCount() - 1);
} else if (draggingItemHoverPosition != INVALID_POSITION) {
mDropListener.drop(dragStartPosition, draggingItemHoverPosition);
}
}
resetViews();
break;
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
int x = (int) ev.getX();
int y = (int) ev.getY();
dragging.drag(x, y);
int position = dragging.calculateHoverPosition();
if (position != INVALID_POSITION) {
if ((action == MotionEvent.ACTION_DOWN) || (position != draggingItemHoverPosition)) {
draggingItemHoverPosition = position;
doExpansion();
}
scrollList(y);
}
break;
}
return true;
}
private void doExpansion() {
int expanItemViewIndex = draggingItemHoverPosition - getFirstVisiblePosition();
if (draggingItemHoverPosition >= dragStartPosition) {
expanItemViewIndex++;
}
// Log.v(LOG_TAG, "Dragging item hovers over position " + draggingItemHoverPosition + ", expand item at index "
// + expanItemViewIndex);
View draggingItemOriginalView = getChildAt(dragStartPosition - getFirstVisiblePosition());
for (int i = 0;; i++) {
View itemView = getChildAt(i);
if (itemView == null) {
break;
}
ViewGroup.LayoutParams params = itemView.getLayoutParams();
int height = LayoutParams.WRAP_CONTENT;
if (itemView.equals(draggingItemOriginalView)) {
height = 1;
} else if (i == expanItemViewIndex) {
height = itemView.getHeight() + dragging.getDraggingItemHeight();
}
params.height = height;
itemView.setLayoutParams(params);
}
}
/**
* Reset view to original height.
*/
private void resetViews() {
for (int i = 0;; i++) {
View v = getChildAt(i);
if (v == null) {
layoutChildren(); // force children to be recreated where needed
v = getChildAt(i);
if (v == null) {
break;
}
}
ViewGroup.LayoutParams params = v.getLayoutParams();
params.height = LayoutParams.WRAP_CONTENT;
v.setLayoutParams(params);
}
}
private void resetScrollBounds(int y) {
int height = getHeight();
if (y >= height / 3) {
mUpperBound = height / 3;
}
if (y <= height * 2 / 3) {
mLowerBound = height * 2 / 3;
}
}
private void scrollList(int y) {
resetScrollBounds(y);
int height = getHeight();
int speed = 0;
if (y > mLowerBound) {
// scroll the list up a bit
speed = y > (height + mLowerBound) / 2 ? 16 : 4;
} else if (y < mUpperBound) {
// scroll the list down a bit
speed = y < mUpperBound / 2 ? -16 : -4;
}
if (speed != 0) {
int ref = pointToPosition(0, height / 2);
if (ref == AdapterView.INVALID_POSITION) {
//we hit a divider or an invisible view, check somewhere else
ref = pointToPosition(0, height / 2 + getDividerHeight() + 64);
}
View v = getChildAt(ref - getFirstVisiblePosition());
if (v != null) {
int pos = v.getTop();
setSelectionFromTop(ref, pos - speed);
}
}
}
public void setDropListener(DropListener l) {
mDropListener = l;
}
public interface DropListener {
void drop(int from, int to);
}
class Dragging {
private Context context;
private WindowManager windowManager;
private WindowManager.LayoutParams mWindowParams;
private ImageView mDragView;
private Bitmap mDragBitmap;
private int coordOffset;
private int mDragPoint; // at what offset inside the item did the user grab it
private int draggingItemHeight;
private int x;
private int y;
private int lastY;
public Dragging(Context context) {
this.context = context;
windowManager = (WindowManager) context.getSystemService("window");
}
/**
* #param y
* #param offset - the difference in y axis between screen coordinates and coordinates in this view
* #param view - which view is dragged
*/
public void start(int y, int offset, View view) {
this.y = y;
lastY = y;
this.coordOffset = offset;
mDragPoint = y - view.getTop();
draggingItemHeight = view.getHeight();
mDragView = new ImageView(context);
mDragView.setBackgroundResource(android.R.drawable.alert_light_frame);
// Create a copy of the drawing cache so that it does not get recycled
// by the framework when the list tries to clean up memory
view.setDrawingCacheEnabled(true);
mDragBitmap = Bitmap.createBitmap(view.getDrawingCache());
mDragView.setImageBitmap(mDragBitmap);
mWindowParams = new WindowManager.LayoutParams();
mWindowParams.gravity = Gravity.TOP;
mWindowParams.x = 0;
mWindowParams.y = y - mDragPoint + coordOffset;
mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
mWindowParams.format = PixelFormat.TRANSLUCENT;
mWindowParams.windowAnimations = 0;
windowManager.addView(mDragView, mWindowParams);
}
public void drag(int x, int y) {
lastY = this.y;
this.x = x;
this.y = y;
mWindowParams.y = y - mDragPoint + coordOffset;
windowManager.updateViewLayout(mDragView, mWindowParams);
}
public void stop() {
if (mDragView != null) {
windowManager.removeView(mDragView);
mDragView.setImageDrawable(null);
mDragView = null;
}
if (mDragBitmap != null) {
mDragBitmap.recycle();
mDragBitmap = null;
}
}
public int getDraggingItemHeight() {
return draggingItemHeight;
}
public int calculateHoverPosition() {
int adjustedY = (int) (y - mDragPoint + (Math.signum(y - lastY) + 2) * draggingItemHeight / 2);
// Log.v(LOG_TAG, "calculateHoverPosition(): lastY=" + lastY + ", y=" + y + ", adjustedY=" + adjustedY);
int pos = myPointToPosition(0, adjustedY);
if (pos >= 0) {
if (pos >= dragStartPosition) {
pos -= 1;
}
}
return pos;
}
}
}

Related

GridView with colspan and rowSpan

I have been trying to make a gridview with drag and drop functionality along with one cell of different size. I have already made the the grid drag and drop and its working fine. you can check the code from here
but I want it to be like this and purely dynamic as I will be draging and dropping the other which will be replaced and resized automatically
Updated with new code that accommodates resizing of cells.
Your question refers to GridView but the code you supplied doesn't mention GridView but uses GridLayout instead, so I am assuming that GridLayout is the right layout.
I have put together a demo using a mocked-up layout with one 2x2 tile. I have modified the code that you have supplied to accommodate the 2x2 tile. Other than the code that I added to implement the 2x2 tile, the only other change to MainAcitivity was to the calculateNextIndex method that uses a different way of calculating the index at an (x, y) position. Layouts and the LongPressListener class were also mocked up since they were not supplied.
Here is a video of the demo:
MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final int ITEMS = 10;
private GridLayout mGrid;
private ScrollView mScrollView;
private ValueAnimator mAnimator;
private Boolean isScroll = false;
private GridLayout.Spec m1xSpec = GridLayout.spec(GridLayout.UNDEFINED, 1);
private GridLayout.Spec m2xSpec = GridLayout.spec(GridLayout.UNDEFINED, 2);
private int mBaseWidth;
private int mBaseHeight;
private int mBaseMargin;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mScrollView = (ScrollView) findViewById(R.id.scrollView);
mScrollView.setSmoothScrollingEnabled(true);
mGrid = (GridLayout) findViewById(R.id.grid);
mGrid.setOnDragListener(new DragListener());
final LayoutInflater inflater = LayoutInflater.from(this);
GridLayout.LayoutParams lp;
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
float dpiToPx = displayMetrics.density;
View view = inflater.inflate(R.layout.item, mGrid, false);
lp = (GridLayout.LayoutParams) view.getLayoutParams();
mBaseWidth = lp.width;
mBaseHeight = lp.height;
mBaseMargin = lp.rightMargin;
for (int i = 0; i < ITEMS; i++) {
final View itemView = inflater.inflate(R.layout.item, mGrid, false);
final TextView text = (TextView) itemView.findViewById(R.id.text);
text.setText(String.valueOf(i + 1));
itemView.setOnLongClickListener(new LongPressListener());
lp = (i == 0) ? make2x2LayoutParams(itemView) : make1x1LayoutParams(itemView);
mGrid.addView(itemView, lp);
}
}
private GridLayout.LayoutParams make2x2LayoutParams(View view) {
GridLayout.LayoutParams lp = (GridLayout.LayoutParams) view.getLayoutParams();
lp.width = mBaseWidth * 2 + 2 * mBaseMargin;
lp.height = mBaseHeight * 2 + 2 * mBaseMargin;
lp.rowSpec = m2xSpec;
lp.columnSpec = m2xSpec;
lp.setMargins(mBaseMargin, mBaseMargin, mBaseMargin, mBaseMargin);
return lp;
}
private GridLayout.LayoutParams make1x1LayoutParams(View view) {
GridLayout.LayoutParams lp = (GridLayout.LayoutParams) view.getLayoutParams();
lp.width = mBaseWidth;
lp.height = mBaseHeight;
lp.setMargins(mBaseMargin, mBaseMargin, mBaseMargin, mBaseMargin);
lp.rowSpec = m1xSpec;
lp.columnSpec = m1xSpec;
return lp;
}
private int mDraggedIndex;
class DragListener implements View.OnDragListener {
#Override
public boolean onDrag(View v, DragEvent event) {
final View view = (View) event.getLocalState();
int index = calculateNextIndex(event.getX(), event.getY());
View child;
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
mDraggedIndex = index;
break;
case DragEvent.ACTION_DRAG_LOCATION:
if (view == v) return true;
// get the new list index
final Rect rect = new Rect();
mScrollView.getHitRect(rect);
final int scrollY = mScrollView.getScrollY();
if (event.getY() - scrollY > mScrollView.getBottom() - 250) {
startScrolling(scrollY, mGrid.getHeight());
} else if (event.getY() - scrollY < mScrollView.getTop() + 250) {
startScrolling(scrollY, 0);
} else {
stopScrolling();
}
child = mGrid.getChildAt(0);
if (index == 0) {
child.setLayoutParams(make1x1LayoutParams(child));
view.setLayoutParams(make2x2LayoutParams(view));
} else if (mDraggedIndex == 0) {
view.setLayoutParams(make1x1LayoutParams(view));
child.setLayoutParams(make2x2LayoutParams(child));
} else {
child.setLayoutParams(make2x2LayoutParams(child));
view.setLayoutParams(make1x1LayoutParams(view));
}
mGrid.removeView(view);
mGrid.addView(view, index);
break;
case DragEvent.ACTION_DROP:
for (int i = 0; i < mGrid.getChildCount(); i++) {
child = mGrid.getChildAt(i);
child.setLayoutParams(make1x1LayoutParams(child));
}
mGrid.removeView(view);
if (index == 0) {
view.setLayoutParams(make2x2LayoutParams(view));
}
mGrid.addView(view, index);
view.setVisibility(View.VISIBLE);
mGrid.getChildAt(0).setLayoutParams(make2x2LayoutParams(mGrid.getChildAt(0)));
break;
case DragEvent.ACTION_DRAG_ENDED:
if (!event.getResult()) {
view.setVisibility(View.VISIBLE);
}
break;
}
return true;
}
}
private void startScrolling(int from, int to) {
if (from != to && mAnimator == null) {
isScroll = true;
mAnimator = new ValueAnimator();
mAnimator.setInterpolator(new OvershootInterpolator());
mAnimator.setDuration(Math.abs(to - from));
mAnimator.setIntValues(from, to);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mScrollView.smoothScrollTo(0, (int) valueAnimator.getAnimatedValue());
}
});
mAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
isScroll = false;
mAnimator = null;
}
});
mAnimator.start();
}
}
private void stopScrolling() {
if (mAnimator != null) {
mAnimator.cancel();
}
}
private int calculateNextIndexOld(float x, float y) {
// calculate which column to move to
final float cellWidth = mGrid.getWidth() / mGrid.getColumnCount();
final int column = (int) (x / cellWidth);
final float cellHeight = mGrid.getHeight() / mGrid.getRowCount();
final int row = (int) Math.floor(y / cellHeight);
int index = row * mGrid.getColumnCount() + column;
if (index >= mGrid.getChildCount()) {
index = mGrid.getChildCount() - 1;
}
Log.d("MainActivity", "<<<<index=" + index);
return index;
}
private int calculateNextIndex(float x, float y) {
// calculate which column to move to
int index;
for (index = 0; index < mGrid.getChildCount(); index++) {
View child = mGrid.getChildAt(index);
Rect rect = new Rect();
child.getHitRect(rect);
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
break;
}
}
if (index >= mGrid.getChildCount()) {
// Move into empty cell? Calculate based upon uniform cell sizes.
index = calculateNextIndexOld(x, y);
}
if (index >= mGrid.getChildCount()) {
// Can't determine where to put it? Add it to the end.
index = mGrid.getChildCount() - 1;
}
return index;
}
}
If you work with the demo a little, you will see that it is possible to move tiles such that a 1x1 tile gap is opened up. This may be OK, but the code may need to be reworked a little if not.
You can try :
https://github.com/askerov/DynamicGrid
I hope it can help your problem!

Gallery like view with center image zoom

Here I need a gallery like view with only three images to be shown at a time on screen. In this the middle image will be larger than the two other images on its sides.
If the user scrolls the view next images will slide on screen as it does in gallery and at a time only three images will be shown out of which the center image should automatically zoom when it is shown on screen and remaining two should be smaller than it.
Here I can't use gallery because it is depreciated in android.
I was able to make a gallery like view with help of viewpager using code on this link. It shows only three images on screen at a time, which fits my one requirement. But i am not able to get the central image that is visible on screen and zoom it. Although I was able to get the clicked image on screen.
Can someone please tell me where do I need to modify this code and what I need to add in it to get the image that is in center from the images shown on screen and zoom it.
I know that there is no center image on screen according to viewpager and it is just showing three images on screen at a time because of modifications in code.
I have also tried:-
GridView with horizontal scroll
HorizontalScrollView with horizontal linear layout
but viewpager seems to be a better solution, because it stops the scrolling with only three items on screen because of viewpager's inherent properties.
and If someone knows any other method to achieve it, please tell me and I'll try it.
P.S. For anyone who wants the full code, I have added it as an answer, which has zoom capability also. Just few additions in accepted answer. :)
Following code will help you to make a gallery like view which will have center lock. It responds to touch and swipe both. It shows three images on the screen at a time and the center image is zoomed.
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
public class CenteringHorizontalScrollView extends HorizontalScrollView implements View.OnTouchListener {
private Context mContext;
private static final int SWIPE_PAGE_ON_FACTOR = 10;
private int mActiveItem;
private float mPrevScrollX;
private boolean mStart;
private int mItemWidth;
View targetLeft, targetRight;
ImageView leftImage, rightImage;
public CenteringHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext=context;
mItemWidth = 100; // or whatever your item width is.
setOnTouchListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getRawX();
boolean handled = false;
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
if (mStart) {
mPrevScrollX = x;
mStart = false;
}
break;
case MotionEvent.ACTION_UP:
mStart = true;
int minFactor = mItemWidth / SWIPE_PAGE_ON_FACTOR;
if ((mPrevScrollX - (float) x) > minFactor) {
if (mActiveItem < getMaxItemCount() - 1) {
mActiveItem = mActiveItem + 1;
}
}else if (((float) x - mPrevScrollX) > minFactor) {
if (mActiveItem > 0) {
mActiveItem = mActiveItem - 1;
}
}
scrollToActiveItem();
handled = true;
break;
}
return handled;
}
private int getMaxItemCount() {
return ((LinearLayout) getChildAt(0)).getChildCount();
}
private LinearLayout getLinearLayout() {
return (LinearLayout) getChildAt(0);
}
/**
* Centers the current view the best it can.
*/
public void centerCurrentItem() {
if (getMaxItemCount() == 0)
return;
int currentX = getScrollX();
View targetChild;
int currentChild = -1;
do {
currentChild++;
targetChild = getLinearLayout().getChildAt(currentChild);
} while (currentChild < getMaxItemCount() && targetChild.getLeft() < currentX);
if (mActiveItem != currentChild) {
mActiveItem = currentChild;
scrollToActiveItem();
}
}
/**
* Scrolls the list view to the currently active child.
*/
private void scrollToActiveItem() {
int maxItemCount = getMaxItemCount();
if (maxItemCount == 0)
return;
int targetItem = Math.min(maxItemCount - 1, mActiveItem);
targetItem = Math.max(0, targetItem);
mActiveItem = targetItem;
// Scroll so that the target child is centered
View targetView = getLinearLayout().getChildAt(targetItem);
ImageView centerImage = (ImageView)targetView;
int height=300;//set size of centered image
LinearLayout.LayoutParams flparams = new LinearLayout.LayoutParams(height, height);
centerImage.setLayoutParams(flparams);
//get the image to left of the centered image
if((targetItem-1)>=0){
targetLeft = getLinearLayout().getChildAt(targetItem-1);
leftImage = (ImageView)targetLeft;
int width=250;//set the size of left image
LinearLayout.LayoutParams leftParams = new LinearLayout.LayoutParams(width,width);
leftParams.setMargins(0, 30, 0, 0);
leftImage.setLayoutParams(leftParams);
}
//get the image to right of the centered image
if((targetItem+1)<maxItemCount){
targetRight = getLinearLayout().getChildAt(targetItem+1);
rightImage = (ImageView)targetRight;
int width=250;//set the size of right image
LinearLayout.LayoutParams rightParams = new LinearLayout.LayoutParams(width,width);
rightParams.setMargins(0, 30, 0, 0);
rightImage.setLayoutParams(rightParams);
}
int targetLeft = targetView.getLeft();
int childWidth = targetView.getRight() - targetLeft;
int width = getWidth() - getPaddingLeft() - getPaddingRight();
int targetScroll = targetLeft - ((width - childWidth) / 2);
super.smoothScrollTo(targetScroll, 0);
}
/**
* Sets the current item and centers it.
* #param currentItem The new current item.
*/
public void setCurrentItemAndCenter(int currentItem) {
mActiveItem = currentItem;
scrollToActiveItem();
}
}
In your xml add the horizontal scroll view like follow:-
<com.yourpackagename.CenteringHorizontalScrollView
android:id="#+id/HSVImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/Horizontalalternative">
<LinearLayout
android:id="#+id/linearImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
</LinearLayout>
</com.yourpackagename.CenteringHorizontalScrollView>
Define a Linear layout in your activity.
LinearLayout imageGallery;
Then get it as follows:-
imageGallery=(LinearLayout)findViewById(R.id.linearImage);
Now you have to add imageView to your LinearLayout. Here I assume that you have images in your drawable folder and you have made an array of ids of your images that you want to add to gallery. So you can do it via following method in your activity:-
for(int i=0; i<lengthOfImageIdArray; i++){
ImageView image=new ImageView(YourActivityName.this);
image.setBackgroundResource(yourArrayName[i]);
imageGallery.addView(image);
}
You can also set the width of images dynamically, so that they fit every screen, with only little extra effort.
Override setPrimaryItem in your ViewPager and make the center item bigger.
What was the issue with using a HorizontalScrollView with a LinearLayout? If it's centering you may be able to do something similar to this (assuming you've
/**
* A centering HSV loosely based on http://iotasol.blogspot.com/2011/08/creating-custom-horizontal-scroll-view.html
*/
public class CenteringHorizontalScrollView extends HorizontalScrollView implements View.OnTouchListener {
private static final int SWIPE_PAGE_ON_FACTOR = 10;
private int mActiveItem;
private float mPrevScrollX;
private boolean mStart;
private int mItemWidth;
public CenteringHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mItemWidth = 100; // or whatever your item width is.
setOnTouchListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getRawX();
boolean handled = false;
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
if (mStart) {
mPrevScrollX = x;
mStart = false;
}
break;
case MotionEvent.ACTION_UP:
mStart = true;
int minFactor = mItemWidth / SWIPE_PAGE_ON_FACTOR;
if ((mPrevScrollX - (float) x) > minFactor) {
if (mActiveItem < getMaxItemCount() - 1) {
mActiveItem = mActiveItem + 1;
}
}
else if (((float) x - mPrevScrollX) > minFactor) {
if (mActiveItem > 0) {
mActiveItem = mActiveItem - 1;
}
}
scrollToActiveItem();
handled = true;
break;
}
return handled;
}
private int getMaxItemCount() {
return ((LinearLayout) getChildAt(0)).getChildCount();
}
private LinearLayout getLinearLayout() {
return (LinearLayout) getChildAt(0);
}
/**
* Centers the current view the best it can.
*/
public void centerCurrentItem() {
if (getMaxItemCount() == 0) {
return;
}
int currentX = getScrollX();
View targetChild;
int currentChild = -1;
do {
currentChild++;
targetChild = getLinearLayout().getChildAt(currentChild);
} while (currentChild < getMaxItemCount() && targetChild.getLeft() < currentX);
if (mActiveItem != currentChild) {
mActiveItem = currentChild;
scrollToActiveItem();
}
}
/**
* Scrolls the list view to the currently active child.
*/
private void scrollToActiveItem() {
int maxItemCount = getMaxItemCount();
if (maxItemCount == 0) {
return;
}
int targetItem = Math.min(maxItemCount - 1, mActiveItem);
targetItem = Math.max(0, targetItem);
mActiveItem = targetItem;
// Scroll so that the target child is centered
View targetView = getLinearLayout().getChildAt(targetItem);
int targetLeft = targetView.getLeft();
int childWidth = targetView.getRight() - targetLeft;
int width = getWidth() - getPaddingLeft() - getPaddingRight();
int targetScroll = targetLeft - ((width - childWidth) / 2);
super.smoothScrollTo(targetScroll, 0);
}
/**
* Sets the current item and centers it.
* #param currentItem The new current item.
*/
public void setCurrentItemAndCenter(int currentItem) {
mActiveItem = currentItem;
scrollToActiveItem();
}
}

Android Gallery zoom in/out

Hi I am using the Gallery widget to show images downloaded from the internet.
to show several images and I would like to have a gradual zoom while people slide up and down on the screen. I know how to implement the touch event the only thing I don't know how to make the whole gallery view grow gradually. I don't want to zoom in on one image I want the whole gallery to zoom in/out gradually.
EDIT3: I manage to zoom the visible part of the gallery but the problem is I need to find a way for the gallery to find out about it and update it's other children too.
What happens is if 3 images are visible then you start zooming and the gallery does get smaller, so do the images but what I would like in this case is more images to be visible but I don't know how to reach this desired effect. Here's the entire code:
public class Gallery1 extends Activity implements OnTouchListener {
private static final String TAG = "GalleryTest";
private float zoom=0.0f;
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
Gallery g;
LinearLayout layout2;
private ImageAdapter ad;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gallery_1);
layout2=(LinearLayout) findViewById(R.id.layout2);
// Reference the Gallery view
g = (Gallery) findViewById(R.id.gallery);
// Set the adapter to our custom adapter (below)
ad=new ImageAdapter(this);
g.setAdapter(ad);
layout2.setOnTouchListener(this);
}
public void zoomList(boolean increase) {
Log.i(TAG, "startig animation");
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(g, "scaleX", zoom),
ObjectAnimator.ofFloat(g, "scaleY", zoom)
);
set.addListener(new AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationEnd(Animator animation) {
}
#Override
public void onAnimationCancel(Animator animation) {
// TODO Auto-generated method stub
}
});
set.setDuration(100).start();
}
public class ImageAdapter extends BaseAdapter {
private static final int ITEM_WIDTH = 136;
private static final int ITEM_HEIGHT = 88;
private final int mGalleryItemBackground;
private final Context mContext;
private final Integer[] mImageIds = {
R.drawable.gallery_photo_1,
R.drawable.gallery_photo_2,
R.drawable.gallery_photo_3,
R.drawable.gallery_photo_4,
R.drawable.gallery_photo_5,
R.drawable.gallery_photo_6,
R.drawable.gallery_photo_7,
R.drawable.gallery_photo_8
};
private final float mDensity;
public ImageAdapter(Context c) {
mContext = c;
// See res/values/attrs.xml for the <declare-styleable> that defines
// Gallery1.
TypedArray a = obtainStyledAttributes(R.styleable.Gallery1);
mGalleryItemBackground = a.getResourceId(
R.styleable.Gallery1_android_galleryItemBackground, 1);
a.recycle();
mDensity = c.getResources().getDisplayMetrics().density;
}
public int getCount() {
return mImageIds.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
convertView = new ImageView(mContext);
imageView = (ImageView) convertView;
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
imageView.setLayoutParams(new Gallery.LayoutParams(
(int) (ITEM_WIDTH * mDensity + 0.5f),
(int) (ITEM_HEIGHT * mDensity + 0.5f)));
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(mImageIds[position]);
return imageView;
}
}
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE
&& event.getPointerCount() > 1) {
midPoint(mid, event);
if(mid.y > start.y){
Log.i(TAG, "Going down (Math.abs(mid.y - start.y)= "+(Math.abs(mid.y - start.y))+" and zoom="+zoom); // going down so increase
if ((Math.abs(mid.y - start.y) > 10) && (zoom<2.5f)){
zoom=zoom+0.1f;
midPoint(start, event);
zoomList(true);
}
return true;
}else if(mid.y < start.y){
Log.i(TAG, "Going up (Math.abs(mid.y - start.y)= "+(Math.abs(mid.y - start.y))+" and zoom="+zoom); //smaller
if ((Math.abs(mid.y - start.y) > 10) &&(zoom>0.1)){
midPoint(start, event);
zoom=zoom-0.1f;
zoomList(false);
}
return true;
}
}
else if (event.getAction() == MotionEvent.ACTION_POINTER_DOWN) {
Log.e(TAG, "Pointer went down: " + event.getPointerCount());
return true;
}
else if (event.getAction() == MotionEvent.ACTION_UP) {
Log.i(TAG, "Pointer going up");
return true;
}
else if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.i(TAG, "Pointer going down");
start.set(event.getX(), event.getY());
return true;
}
return false;
// indicate event was handled or not
}
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
I realise I will probably have to extend the Gallery or even another View group or create my own class but I don't know where to start: which method use the one responsible for scaling...
EDIT4: I don't know if he question is clear enough. Here is an example of states:
State one: initial state, we have 3 images in view
State 2: we detect vertical touches going up with 2 fingers = we have to zoom out
state 3: we start zooming = animation on the gallery or on the children???
state 4: gallery detects that it's 3 children are smaller
state 5: gallery adds 1 /more children according to the new available space
LAST UPDATE:
Thanks to all that have posted but I have finally reached a conclusion and that is to not use Gallery at all:
1. It's deprecated
2. It's not customizable enough for my case
If you want to animate several images at once you may want to consider using OpenGl, I am using libgdx library:
https://github.com/libgdx/libgdx
The following ScalingGallery implementation might be of help.
This gallery subclass overrides the getChildStaticTransformation(View child, Transformation t) method in which the scaling is performed. You can further customize the scaling parameters to fit your own needs.
Please note the ScalingGalleryItemLayout.java class. This is necessary because after you have performed the scaling operationg on the child views, their hit boxes are no longer valid so they must be updated from with the getChildStaticTransformation(View child, Transformation t) method.
This is done by wrapping each gallery item in a ScalingGalleryItemLayout which extends a LinearLayout. Again, you can customize this to fit your own needs if a LinearLayout does not meet your needs for layout out your gallery items.
File : /src/com/example/ScalingGallery.java
/**
* A Customized Gallery component which alters the size and position of its items based on their position in the Gallery.
*/
public class ScalingGallery extends Gallery {
public static final int ITEM_SPACING = -20;
private static final float SIZE_SCALE_MULTIPLIER = 0.25f;
private static final float ALPHA_SCALE_MULTIPLIER = 0.5f;
private static final float X_OFFSET = 20.0f;
/**
* Implemented by child view to adjust the boundaries after it has been matrix transformed.
*/
public interface SetHitRectInterface {
public void setHitRect(RectF newRect);
}
/**
* #param context
* Context that this Gallery will be used in.
* #param attrs
* Attributes for this Gallery (via either xml or in-code)
*/
public ScalingGallery(Context context, AttributeSet attrs) {
super(context, attrs);
setStaticTransformationsEnabled(true);
setChildrenDrawingOrderEnabled(true);
}
/**
* {#inheritDoc}
*
* #see #setStaticTransformationsEnabled(boolean)
*
* This is where the scaling happens.
*/
protected boolean getChildStaticTransformation(View child, Transformation t) {
child.invalidate();
t.clear();
t.setTransformationType(Transformation.TYPE_BOTH);
// Position of the child in the Gallery (... +2 +1 0 -1 -2 ... 0 being the middle)
final int childPosition = getSelectedItemPosition() - getPositionForView(child);
final int childPositionAbs = (int) Math.abs(childPosition);
final float left = child.getLeft();
final float top = child.getTop();
final float right = child.getRight();
final float bottom = child.getBottom();
Matrix matrix = t.getMatrix();
RectF modifiedHitBox = new RectF();
// Change alpha, scale and translate non-middle child views.
if (childPosition != 0) {
final int height = child.getMeasuredHeight();
final int width = child.getMeasuredWidth();
// Scale the size.
float scaledSize = 1.0f - (childPositionAbs * SIZE_SCALE_MULTIPLIER);
if (scaledSize < 0) {
scaledSize = 0;
}
matrix.setScale(scaledSize, scaledSize);
float moveX = 0;
float moveY = 0;
// Moving from right to left -- linear move since the scaling is done with respect to top-left corner of the view.
if (childPosition < 0) {
moveX = ((childPositionAbs - 1) * SIZE_SCALE_MULTIPLIER * width) + X_OFFSET;
moveX *= -1;
} else { // Moving from left to right -- sum of the previous positions' x displacements.
// X(n) = X(0) + X(1) + X(2) + ... + X(n-1)
for (int i = childPositionAbs; i > 0; i--) {
moveX += (i * SIZE_SCALE_MULTIPLIER * width);
}
moveX += X_OFFSET;
}
// Moving down y-axis is linear.
moveY = ((childPositionAbs * SIZE_SCALE_MULTIPLIER * height) / 2);
matrix.postTranslate(moveX, moveY);
// Scale alpha value.
final float alpha = (1.0f / childPositionAbs) * ALPHA_SCALE_MULTIPLIER;
t.setAlpha(alpha);
// Calculate new hit box. Since we moved the child, the hitbox is no longer lined up with the new child position.
final float newLeft = left + moveX;
final float newTop = top + moveY;
final float newRight = newLeft + (width * scaledSize);
final float newBottom = newTop + (height * scaledSize);
modifiedHitBox = new RectF(newLeft, newTop, newRight, newBottom);
} else {
modifiedHitBox = new RectF(left, top, right, bottom);
}
// update child hit box so you can tap within the child's boundary
((SetHitRectInterface) child).setHitRect(modifiedHitBox);
return true;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Helps to smooth out jittering during scrolling.
// read more - http://www.unwesen.de/2011/04/17/android-jittery-scrolling-gallery/
final int viewsOnScreen = getLastVisiblePosition() - getFirstVisiblePosition();
if (viewsOnScreen <= 0) {
super.onLayout(changed, l, t, r, b);
}
}
private int mLastDrawnPosition;
#Override
protected int getChildDrawingOrder(int childCount, int i) {
//Reset the last position variable every time we are starting a new drawing loop
if (i == 0) {
mLastDrawnPosition = 0;
}
final int centerPosition = getSelectedItemPosition() - getFirstVisiblePosition();
if (i == childCount - 1) {
return centerPosition;
} else if (i >= centerPosition) {
mLastDrawnPosition++;
return childCount - mLastDrawnPosition;
} else {
return i;
}
}
}
File : /src/com/example/ScalingGalleryItemLayout.java
public class ScalingGalleryItemLayout extends LinearLayout implements SetHitRectInterface {
public ScalingGalleryItemLayout(Context context) {
super(context);
}
public ScalingGalleryItemLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScalingGalleryItemLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private Rect mTransformedRect;
#Override
public void setHitRect(RectF newRect) {
if (newRect == null) {
return;
}
if (mTransformedRect == null) {
mTransformedRect = new Rect();
}
newRect.round(mTransformedRect);
}
#Override
public void getHitRect(Rect outRect) {
if (mTransformedRect == null) {
super.getHitRect(outRect);
} else {
outRect.set(mTransformedRect);
}
}
}
File : /res/layout/ScaledGalleryItemLayout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.ScalingGalleryItemLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gallery_item_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
android:padding="5dp" >
<ImageView
android:id="#+id/gallery_item_image"
android:layout_width="360px"
android:layout_height="210px"
android:layout_gravity="center"
android:antialias="true"
android:background="#drawable/gallery_item_button_selector"
android:cropToPadding="true"
android:padding="35dp"
android:scaleType="centerInside" />
<TextView
android:id="#+id/gallery_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#drawable/white"
android:textSize="30sp" />
</com.example.ScalingGalleryItemLayout>
To keep the state of the animation after it is done, just do this on your animation:
youranim.setFillAfter(true);
Edit :
In my project, I use this method and i think, it's help you :
http://developer.sonymobile.com/wp/2011/04/12/how-to-take-advantage-of-the-pinch-to-zoom-feature-in-your-xperia%E2%84%A2-10-apps-part-1/
U can do Image Zoom pinch option for gallery also.
by using below code lines:
you can download the example.
https://github.com/alvinsj/android-image-gallery/downloads
I hope this example will help to u..if u have any queries ask me.....
This is solution
integrate gallery component in android with gesture-image library
gesture-imageView
And here is full sample code
SampleCode

Fast Scroll display problem with ListAdapter and SectionIndexer

I have a list of events which are seperated by month and year (Jun 2010, Jul 2010 etc.). I have enabled fast scrolling because the list is really long. I've also implemented SectionIndexer so that people can see what month and year they are currently viewing when scrolling down the list of events at speed.
I don't have any problem with the implementation, just how the information is shown. Fast scrolling with SectionIndexer seems to only really be able to support a label with a single letter. If the list was alphabetised this would be perfect, however I want it to display a bit more text.
If you look at the screenshot bellow you'll see the problem I'm having.
(source: matto1990.com)
What I want to know is: is it possible to change how the text in the centre of the screen is displayed. Can I change it somehow to make it look right (with the background covering all of the text).
Thanks in advance. If you need any clarification, or code just ask.
EDIT: Full sample code for this solution available here.
I had this same problem - I needed to display full text in the overlay rectangle rather than just a single character. I managed to solve it using the following code as an example: http://code.google.com/p/apps-for-android/source/browse/trunk/RingsExtended/src/com/example/android/rings_extended/FastScrollView.java
The author said that this was copied from the Contacts app, which apparently uses its own implementation rather than just setting fastScrollEnabled="true" on the ListView. I altered it a little bit so that you can customize the overlay rectangle width, overlay rectangle height, overlay text size, and scroll thumb width.
For the record, the final result looks like this: http://nolanwlawson.files.wordpress.com/2011/03/pokedroid_1.png
All you need to do is add these values to your res/values/attrs.xml:
<declare-styleable name="CustomFastScrollView">
<attr name="overlayWidth" format="dimension"/>
<attr name="overlayHeight" format="dimension"/>
<attr name="overlayTextSize" format="dimension"/>
<attr name="overlayScrollThumbWidth" format="dimension"/>
</declare-styleable>
And then use this CustomFastScrollView instead of the one in the link:
public class CustomFastScrollView extends FrameLayout
implements OnScrollListener, OnHierarchyChangeListener {
private Drawable mCurrentThumb;
private Drawable mOverlayDrawable;
private int mThumbH;
private int mThumbW;
private int mThumbY;
private RectF mOverlayPos;
// custom values I defined
private int mOverlayWidth;
private int mOverlayHeight;
private float mOverlayTextSize;
private int mOverlayScrollThumbWidth;
private boolean mDragging;
private ListView mList;
private boolean mScrollCompleted;
private boolean mThumbVisible;
private int mVisibleItem;
private Paint mPaint;
private int mListOffset;
private Object [] mSections;
private String mSectionText;
private boolean mDrawOverlay;
private ScrollFade mScrollFade;
private Handler mHandler = new Handler();
private BaseAdapter mListAdapter;
private boolean mChangedBounds;
public static interface SectionIndexer {
Object[] getSections();
int getPositionForSection(int section);
int getSectionForPosition(int position);
}
public CustomFastScrollView(Context context) {
super(context);
init(context, null);
}
public CustomFastScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public CustomFastScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void useThumbDrawable(Drawable drawable) {
mCurrentThumb = drawable;
mThumbW = mOverlayScrollThumbWidth;//mCurrentThumb.getIntrinsicWidth();
mThumbH = mCurrentThumb.getIntrinsicHeight();
mChangedBounds = true;
}
private void init(Context context, AttributeSet attrs) {
// set all attributes from xml
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.CustomFastScrollView);
mOverlayHeight = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayHeight, 0);
mOverlayWidth = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayWidth, 0);
mOverlayTextSize = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayTextSize, 0);
mOverlayScrollThumbWidth = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayScrollThumbWidth, 0);
}
// Get both the scrollbar states drawables
final Resources res = context.getResources();
Drawable thumbDrawable = res.getDrawable(R.drawable.scrollbar_handle_accelerated_anim2);
useThumbDrawable(thumbDrawable);
mOverlayDrawable = res.getDrawable(android.R.drawable.alert_dark_frame);
mScrollCompleted = true;
setWillNotDraw(false);
// Need to know when the ListView is added
setOnHierarchyChangeListener(this);
mOverlayPos = new RectF();
mScrollFade = new ScrollFade();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setTextSize(mOverlayTextSize);
mPaint.setColor(0xFFFFFFFF);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
}
private void removeThumb() {
mThumbVisible = false;
// Draw one last time to remove thumb
invalidate();
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (!mThumbVisible) {
// No need to draw the rest
return;
}
final int y = mThumbY;
final int viewWidth = getWidth();
final CustomFastScrollView.ScrollFade scrollFade = mScrollFade;
int alpha = -1;
if (scrollFade.mStarted) {
alpha = scrollFade.getAlpha();
if (alpha < ScrollFade.ALPHA_MAX / 2) {
mCurrentThumb.setAlpha(alpha * 2);
}
int left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX;
mCurrentThumb.setBounds(left, 0, viewWidth, mThumbH);
mChangedBounds = true;
}
canvas.translate(0, y);
mCurrentThumb.draw(canvas);
canvas.translate(0, -y);
// If user is dragging the scroll bar, draw the alphabet overlay
if (mDragging && mDrawOverlay) {
mOverlayDrawable.draw(canvas);
final Paint paint = mPaint;
float descent = paint.descent();
final RectF rectF = mOverlayPos;
canvas.drawText(mSectionText, (int) (rectF.left + rectF.right) / 2,
(int) (rectF.bottom + rectF.top) / 2 + descent, paint);
} else if (alpha == 0) {
scrollFade.mStarted = false;
removeThumb();
} else {
invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mCurrentThumb != null) {
mCurrentThumb.setBounds(w - mThumbW, 0, w, mThumbH);
}
final RectF pos = mOverlayPos;
pos.left = (w - mOverlayWidth) / 2;
pos.right = pos.left + mOverlayWidth;
pos.top = h / 10; // 10% from top
pos.bottom = pos.top + mOverlayHeight;
mOverlayDrawable.setBounds((int) pos.left, (int) pos.top,
(int) pos.right, (int) pos.bottom);
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
if (totalItemCount - visibleItemCount > 0 && !mDragging) {
mThumbY = ((getHeight() - mThumbH) * firstVisibleItem) / (totalItemCount - visibleItemCount);
if (mChangedBounds) {
final int viewWidth = getWidth();
mCurrentThumb.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH);
mChangedBounds = false;
}
}
mScrollCompleted = true;
if (firstVisibleItem == mVisibleItem) {
return;
}
mVisibleItem = firstVisibleItem;
if (!mThumbVisible || mScrollFade.mStarted) {
mThumbVisible = true;
mCurrentThumb.setAlpha(ScrollFade.ALPHA_MAX);
}
mHandler.removeCallbacks(mScrollFade);
mScrollFade.mStarted = false;
if (!mDragging) {
mHandler.postDelayed(mScrollFade, 1500);
}
}
private void getSections() {
Adapter adapter = mList.getAdapter();
if (adapter instanceof HeaderViewListAdapter) {
mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount();
adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
}
if (adapter instanceof SectionIndexer) {
mListAdapter = (BaseAdapter) adapter;
mSections = ((SectionIndexer) mListAdapter).getSections();
}
}
public void onChildViewAdded(View parent, View child) {
if (child instanceof ListView) {
mList = (ListView)child;
mList.setOnScrollListener(this);
getSections();
}
}
public void onChildViewRemoved(View parent, View child) {
if (child == mList) {
mList = null;
mListAdapter = null;
mSections = null;
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mThumbVisible && ev.getAction() == MotionEvent.ACTION_DOWN) {
if (ev.getX() > getWidth() - mThumbW && ev.getY() >= mThumbY &&
ev.getY() <= mThumbY + mThumbH) {
mDragging = true;
return true;
}
}
return false;
}
private void scrollTo(float position) {
int count = mList.getCount();
mScrollCompleted = false;
final Object[] sections = mSections;
int sectionIndex;
if (sections != null && sections.length > 1) {
final int nSections = sections.length;
int section = (int) (position * nSections);
if (section >= nSections) {
section = nSections - 1;
}
sectionIndex = section;
final SectionIndexer baseAdapter = (SectionIndexer) mListAdapter;
int index = baseAdapter.getPositionForSection(section);
// Given the expected section and index, the following code will
// try to account for missing sections (no names starting with..)
// It will compute the scroll space of surrounding empty sections
// and interpolate the currently visible letter's range across the
// available space, so that there is always some list movement while
// the user moves the thumb.
int nextIndex = count;
int prevIndex = index;
int prevSection = section;
int nextSection = section + 1;
// Assume the next section is unique
if (section < nSections - 1) {
nextIndex = baseAdapter.getPositionForSection(section + 1);
}
// Find the previous index if we're slicing the previous section
if (nextIndex == index) {
// Non-existent letter
while (section > 0) {
section--;
prevIndex = baseAdapter.getPositionForSection(section);
if (prevIndex != index) {
prevSection = section;
sectionIndex = section;
break;
}
}
}
// Find the next index, in case the assumed next index is not
// unique. For instance, if there is no P, then request for P's
// position actually returns Q's. So we need to look ahead to make
// sure that there is really a Q at Q's position. If not, move
// further down...
int nextNextSection = nextSection + 1;
while (nextNextSection < nSections &&
baseAdapter.getPositionForSection(nextNextSection) == nextIndex) {
nextNextSection++;
nextSection++;
}
// Compute the beginning and ending scroll range percentage of the
// currently visible letter. This could be equal to or greater than
// (1 / nSections).
float fPrev = (float) prevSection / nSections;
float fNext = (float) nextSection / nSections;
index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev)
/ (fNext - fPrev));
// Don't overflow
if (index > count - 1) index = count - 1;
mList.setSelectionFromTop(index + mListOffset, 0);
} else {
int index = (int) (position * count);
mList.setSelectionFromTop(index + mListOffset, 0);
sectionIndex = -1;
}
if (sectionIndex >= 0) {
String text = mSectionText = sections[sectionIndex].toString();
mDrawOverlay = (text.length() != 1 || text.charAt(0) != ' ') &&
sectionIndex < sections.length;
} else {
mDrawOverlay = false;
}
}
private void cancelFling() {
// Cancel the list fling
MotionEvent cancelFling = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
mList.onTouchEvent(cancelFling);
cancelFling.recycle();
}
#Override
public boolean onTouchEvent(MotionEvent me) {
if (me.getAction() == MotionEvent.ACTION_DOWN) {
if (me.getX() > getWidth() - mThumbW
&& me.getY() >= mThumbY
&& me.getY() <= mThumbY + mThumbH) {
mDragging = true;
if (mListAdapter == null && mList != null) {
getSections();
}
cancelFling();
return true;
}
} else if (me.getAction() == MotionEvent.ACTION_UP) {
if (mDragging) {
mDragging = false;
final Handler handler = mHandler;
handler.removeCallbacks(mScrollFade);
handler.postDelayed(mScrollFade, 1000);
return true;
}
} else if (me.getAction() == MotionEvent.ACTION_MOVE) {
if (mDragging) {
final int viewHeight = getHeight();
mThumbY = (int) me.getY() - mThumbH + 10;
if (mThumbY < 0) {
mThumbY = 0;
} else if (mThumbY + mThumbH > viewHeight) {
mThumbY = viewHeight - mThumbH;
}
// If the previous scrollTo is still pending
if (mScrollCompleted) {
scrollTo((float) mThumbY / (viewHeight - mThumbH));
}
return true;
}
}
return super.onTouchEvent(me);
}
public class ScrollFade implements Runnable {
long mStartTime;
long mFadeDuration;
boolean mStarted;
static final int ALPHA_MAX = 200;
static final long FADE_DURATION = 200;
void startFade() {
mFadeDuration = FADE_DURATION;
mStartTime = SystemClock.uptimeMillis();
mStarted = true;
}
int getAlpha() {
if (!mStarted) {
return ALPHA_MAX;
}
int alpha;
long now = SystemClock.uptimeMillis();
if (now > mStartTime + mFadeDuration) {
alpha = 0;
} else {
alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration);
}
return alpha;
}
public void run() {
if (!mStarted) {
startFade();
invalidate();
}
if (getAlpha() > 0) {
final int y = mThumbY;
final int viewWidth = getWidth();
invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
} else {
mStarted = false;
removeThumb();
}
}
}
}
You can also tweak the translucency of the scroll thumb using ALPHA_MAX.
Then put something like this in your layout xml file:
<com.myapp.CustomFastScrollView android:layout_width="wrap_content"
android:layout_height="fill_parent"
myapp:overlayWidth="175dp" myapp:overlayHeight="110dp" myapp:overlayTextSize="36dp"
myapp:overlayScrollThumbWidth="60dp" android:id="#+id/fast_scroll_view">
<ListView android:id="#android:id/list" android:layout_width="wrap_content"
android:layout_height="fill_parent"/>
<TextView android:id="#android:id/empty"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="" />
</com.myapp.CustomFastScrollView>
Don't forget to declare your attributes in that layout xml file as well:
... xmlns:myapp= "http://schemas.android.com/apk/res/com.myapp" ...
You'll also need to grab the R.drawable.scrollbar_handle_accelerated_anim2 drawables from that Android source code. The link above only contains the mdpi one.
The FastScroller widget is responsible for drawing the overlay. You should probably take a look at its source:
https://android.googlesource.com/platform/frameworks/base/+/gingerbread-release/core/java/android/widget/FastScroller.java
Search for comment:
// If user is dragging the scroll bar, draw the alphabet overlay

Horizontal "tab"ish scroll between views

I'm interested in creating a horizontal scroll view that "snaps" to the viewed item, so only one item is ever shown at a time. The user can touch-drag left/right and will see previous/next views, switching to it if there's enough velocity. This interaction is exactly like what the new weather/news widget that comes with the Nexus One does for navigating between its "tabs".
Are there any existing view widgets that do this?
Update: found a copy of the news/weather widget (GenieWidget) and they seem to have implemented their own widget to accomplish this which they call com.google.android.apps.genie.geniewidget.ui.FlingableLinearLayout which is part of their own custom com.google.android.apps.genie.geniewidget.ui.TabView. As that source isn't available, that's not looking too hopeful a direction.
(update 20110905: Official android tools now do this better)
I cloned Eric Taix's http://code.google.com/p/andro-views/ on github
https://github.com/olibye/AndroViews
Then applied the patches from above:
JonO's patch
Tom de Waard's patch
Split into a library and an example, allowing simple inclusion in other projects
I would have made this comment above, however I didn't appear able to comment on JonO's answer
Don't look at the News and weather implementation, it has a couple of flaws. You can however use the source code of the Home app (called Launcher or Launcher2), at android.git.kernel.org. The widget we use to do the scrolling on Home is in Workspace.java.
Eric Taix has done most of the grunt work of stripping the Workspace into a WorkspaceView that can be reused. It can be found here: http://code.google.com/p/andro-views/
The version as of posting does what it's supposed to in the emulator, but on real hardware it sometimes gets stuck between views instead of snapping back--I have emailed him a patch for this (which he is testing before committing, as of the date of posting this) that should make it behave exactly as the Workspace does.
If the patch doesn't appear there shortly, I will post it separately.
As promised, since it hasn't yet appeared, here is my patched version:
/**
* 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;
}
}
I am not aware of Nexus one but, i can suggest u the gallery view. It is perfectly apt one for your requirement according to your above explanation.
is this what are you looking for ?
http://code.google.com/p/mobyfactory-uiwidgets-android/
I tried many of the code examples we can find here and there online, and even if almost all were good, they were not handling properly what I had in mind.
In fact I have several ScrollViews side by side, the screen displaying only one. When the user scrolls vertically, I want the ScrollView to scroll; when the user scrolls horizontally, I want the ViewFlow (the global layout I'm using) to snap to the previous/next ScrollView.
I got something almost close to what I wanted, but that'll do it for now. I know that this was answered long time ago, but I want to share what I made, so here it is.
It's just a class that derives ScrollView and implements onInterceptTouchEvent and 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;
}
The code is not perfect and requires a little bit more polish, but it's functional. Almost like Genie Widget. (I wish this guy was open source!) Especially around the scrolling detection, it's not using exactly the touch slop, and it's comparing the move between the X and Y axis. But this is easily tuneable.
And I am using this very good code for my ViewFlow: https://github.com/pakerfeldt/android-viewflow

Categories

Resources