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
Related
Currently I have a Button in a layout, however an assigned OnClickListener never calls back to the onClick method.
Is it possible to intercept the click of a Button in a layout assigned to a MarkerView?
I have finished my app with clickable marker view. My solution is that we'll create a subclass of LineChart (or other chart), then let override onTouchEvent and detect the touch location.
public class MyChart extends LineChart {
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean handled = true;
// if there is no marker view or drawing marker is disabled
if (isShowingMarker() && this.getMarker() instanceof ChartInfoMarkerView){
ChartInfoMarkerView markerView = (ChartInfoMarkerView) this.getMarker();
Rect rect = new Rect((int)markerView.drawingPosX,(int)markerView.drawingPosY,(int)markerView.drawingPosX + markerView.getWidth(), (int)markerView.drawingPosY + markerView.getHeight());
if (rect.contains((int) event.getX(),(int) event.getY())) {
// touch on marker -> dispatch touch event in to marker
markerView.dispatchTouchEvent(event);
}else{
handled = super.onTouchEvent(event);
}
}else{
handled = super.onTouchEvent(event);
}
return handled;
}
private boolean isShowingMarker(){
return mMarker != null && isDrawMarkersEnabled() && valuesToHighlight();
}
}
public class ChartInfoMarkerView extends MarkerView {
#BindView(R.id.markerContainerView)
LinearLayout markerContainerView;
protected float drawingPosX;
protected float drawingPosY;
private static final int MAX_CLICK_DURATION = 500;
private long startClickTime;
/**
* The constructor
*
* #param context
* #param layoutResource
*/
public ChartInfoMarkerView(Context context, int layoutResource) {
super(context, layoutResource);
ButterKnife.bind(this);
markerContainerView.setClickable(true);
markerContainerView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("MARKER","click");
}
});
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
startClickTime = Calendar.getInstance().getTimeInMillis();
break;
}
case MotionEvent.ACTION_UP: {
long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
if(clickDuration < MAX_CLICK_DURATION) {
markerContainerView.performClick();
}
}
}
return super.onTouchEvent(event);
}
#Override
public void draw(Canvas canvas, float posX, float posY) {
super.draw(canvas, posX, posY);
MPPointF offset = getOffsetForDrawingAtPoint(posX, posY);
this.drawingPosX = posX + offset.x;
this.drawingPosY = posY + offset.y;
}
}
Using the library it appears not to be possible, however a solution of sorts is to show a View or ViewGroup over the chart which has a Button in it. You’ll need to set up an empty layout for the MarkerView and wrap your Chart in a ViewGroup such as a RelativeLayout.
Define a listener such as this in your CustomMarkerView:
public interface Listener {
/**
* A callback with the x,y position of the marker
* #param x the x in pixels
* #param y the y in pixels
*/
void onMarkerViewLayout(int x, int y);
}
Set up some member variables:
private Listener mListener;
private int mLayoutX;
private int mLayoutY;
private int mMarkerVisibility;
In your constructor require a listener:
/**
* Constructor. Sets up the MarkerView with a custom layout resource.
* #param context a context
* #param layoutResource the layout resource to use for the MarkerView
* #param listener listens for the bid now click
*/
public SQUChartMarkerView(Context context, int layoutResource, Listener listener) {
super(context, layoutResource);
mListener = listener;
}
Store the location the marker should be when the values are set:
#Override public int getXOffset(float xpos) {
mLayoutX = (int) (xpos - (getWidth() / 2));
return -getWidth() / 2;
}
#Override public int getYOffset(float ypos) {
mLayoutY = (int) (ypos - getWidth());
return -getHeight();
}
Then override onDraw to determine when you should draw your layout:
#Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mMarkerVisibility == View.VISIBLE) mListener.onMarkerViewLayout(mLayoutX, mLayoutY);
}
I added an option to change the state of the marker:
public void setMarkerVisibility(int markerVisibility) {
mMarkerVisibility = markerVisibility;
}
Where you listen for marker being laid out, get your layout (eg. inflate it or you may have it as a member variable), make sure you measure it, then set the margins. In this case I am using getParent() as the chart and the layout for the marker share the same parent. I have a BarChart so my margins may be different from yours.
#Override public void onMarkerViewLayout(int x, int y) {
if(getParent() == null || mChartListener.getAmount() == null) return;
// remove the marker
((ViewGroup) getParent()).removeView(mMarkerLayout);
((ViewGroup) getParent()).addView(mMarkerLayout);
// measure the layout
// if this is not done, the first calculation of the layout parameter margins
// will be incorrect as the layout at this point has no height or width
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(((ViewGroup) getParent()).getWidth(), View.MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(((ViewGroup) getParent()).getHeight(), View.MeasureSpec.UNSPECIFIED);
mMarkerLayout.measure(widthMeasureSpec, heightMeasureSpec);
// set up layout parameters so our marker is in the same position as the mpchart marker would be (based no the x and y)
RelativeLayout.LayoutParams lps = (RelativeLayout.LayoutParams) mMarkerLayout.getLayoutParams();
lps.height = FrameLayout.LayoutParams.WRAP_CONTENT;
lps.width = FrameLayout.LayoutParams.WRAP_CONTENT;
lps.leftMargin = x - mMarkerLayout.getMeasuredWidth() / 2;
lps.topMargin = y - mMarkerLayout.getMeasuredHeight();
}
Hope this helps.
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();
}
}
I have listview similar to this:
Add code, it isn't much, but maybe helpful:
CustomAdapter:
public class MyAdapter extends BaseAdapter{
private LayoutInflater mInflater;
private Animation fadeIn;
private Animation fadeOut;
public MyAdapter(Activity c) {
mInflater = c.getLayoutInflater();
fadeIn = AnimationUtils.loadAnimation(c.getApplicationContext(), R.anim.alpha_show);
fadeIn.setFillAfter(true);
fadeOut = AnimationUtils.loadAnimation(c.getApplicationContext(), R.anim.alpha_dissappear);
fadeOut.setFillAfter(true);
}
public int getCount() {
return MainButtonsList.getList().getSize() +
GlobalPrefs.getEmptyRowsAtEnd() +
GlobalPrefs.getEmptyRowsAtStart();
}
public View getView(int position, View v, ViewGroup parent) {
View convertView = v;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.main_list_item, null);
}
ImageView iv = (ImageView) convertView.findViewById(R.id.image);
if ((position > GlobalPrefs.getEmptyRowsAtStart() - 1) && (position < getCount() - GlobalPrefs.getEmptyRowsAtEnd())) {
iv.setImageResource(MainButtonsList.getList().getListImageResource(position - GlobalPrefs.getEmptyRowsAtStart()));
iv.setAlpha(255);
iv.setTag(MainButtonsList.UNPRESSED_BUTTON_TAG);
} else {
iv.setTag(MainButtonsList.UNUSED_BUTTON_TAG);
iv.setAlpha(0);
iv.setVisibility(0);
iv.setClickable(false);
iv.setImageResource(R.drawable.logo_list_null);
}
iv.setMaxHeight(GlobalPrefs.getRowHeight());
iv.setMaxWidth(GlobalPrefs.getRowWidth());
iv.setBackgroundResource(0);
return convertView;
}
public Object getItem(int position) {
return MainButtonsList.getList().getObject(position);
}
public long getItemId(int position) {
return position;
}
}
Custom ListView:
public class CopyOfListView3d extends ListView{
private final Camera mCamera = new Camera();
private final Matrix mMatrix = new Matrix();
private Context context;
private Paint mPaint;
public CopyOfListView3d(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
this.setChildrenDrawingOrderEnabled(true);
}
#Override
protected int getChildDrawingOrder (int childCount, int i) {
//sets order number to each child, so makes overlap and center is always on top
}
#Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// get top left coordinates
boolean isCenter = false;
final int top = child.getTop();
final int bottom = child.getBottom();
Bitmap bitmap = child.getDrawingCache();
if (bitmap == null) {
child.setDrawingCacheEnabled(true);
child.buildDrawingCache();
bitmap = child.getDrawingCache();
}
final int centerY = child.getHeight() / 2;
final int centerX = child.getWidth() / 2;
final int radius = getHeight() / 2;
final int absParentCenterY = getTop() + getHeight() / 2;
final int absChildCenterY = child.getTop() + centerY;
final int distanceY = (absParentCenterY - absChildCenterY) / 2;
final int absDistance = Math.min(radius, Math.abs(distanceY));
final float translateZ = (float) Math.sqrt((radius * radius) - (absDistance * absDistance));
mCamera.save();
float myTranslateX = (float) (translateZ * (1.5f));
int density = GlobalPrefs.getDensity();
if (density < DisplayMetrics.DENSITY_LOW) {
myTranslateX = (float) myTranslateX - 80;
} else if (density == DisplayMetrics.DENSITY_LOW) {
myTranslateX = (float) myTranslateX - GlobalPrefs.getScreenWidth() + density + 40;
} else if (density <= DisplayMetrics.DENSITY_MEDIUM) {
myTranslateX = (float) myTranslateX - ((float)(GlobalPrefs.getScreenWidth()*0.75)) + density/2;
} else
if (density <= DisplayMetrics.DENSITY_HIGH) {
myTranslateX = (float) myTranslateX - 320;
} else
if (density > DisplayMetrics.DENSITY_HIGH) {
//Log.i("density", "this is more than high");
myTranslateX = (float) myTranslateX;
}
if ((top < absParentCenterY) && (bottom > absParentCenterY)) {
//make center row bigger
isCenter = true;
mCamera.translate((float) myTranslateX, 0, (float) -160);//130
child.setPressed(true);
child.setTag(MainButtonsList.PRESSED_BUTTON_TAG);
}
else {
//top
child.setTag(MainButtonsList.UNPRESSED_BUTTON_TAG);
child.setPressed(false);
mCamera.translate((float) myTranslateX, 0, -150);//120;
};
mCamera.getMatrix(mMatrix);
mCamera.restore();
// create and initialize the paint object
if (mPaint == null) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
}
//calculates alpha for each row, so far from center, rows are barely visible
mPaint.setAlpha(calculateAlpha(absChildCenterY));
mMatrix.postTranslate((float) (-centerX * 1.5f), top);
canvas.drawBitmap(bitmap, mMatrix, mPaint);
return false;
}
}
They are made of ImageViews where white color is selected state and blue color is unselected state and I change selected/unselected by screen coordinates, so center always is selected.
I have custom 3dListView which extends ListView and made custom adapter, where i set imageviews to rows. Code is too much to show, but there are pretty simple
One of these could be helpfull:
1) How can I make fade in (maybe also fade out) for center row (I can find which row is center when giving parent or childCount)? (Better)
2) It also could be helpfull if there is any OnStateChange listener, which listens to view's presses. Then I could fade in anytime when view becomes pressed and fadeout, when looses press.
I found an answer myself. Added this code piece to my custom ListView in drawChild method end:
final ImageView iv = (ImageView) child.findViewById(R.id.image);
child.setTag(MainButtonsList.PRESSED_BUTTON_TAG);
Animation anim = fadeOut();
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub
}
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub
}
public void onAnimationEnd(Animation animation) {
child.setPressed(true);
iv.startAnimation(fadeIn());
}
});
iv.clearAnimation();
iv.startAnimation(anim);
In list item layout, added another image, which put under item and now it looks prety nice.
You'll want to do the fading in the adapter for the listview.
In your adapter
private List<YourListObject> deleteItems;
// Constructor to init list and other vars
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Draw stuff
// including your object
for(YourListObject obj : deleteItems){
if(currentObj.equals(obj)){
Animation fadeOutAnimation = new AlphaAnimation(1.0f, 0.0f);
fadeOutAnimation.setDuration(300);
DeleteAnimationListener listener = new DeleteAnimationListener(currentObj);
fadeOutAnimation.setAnimationListener(listener);
someView.startAnimation(fadeOutAnimation);
}
}
return someView;
}
public void remove(YourListObject obj){
deleteItems.add(obj);
notifyDataSetChanged();
}
protected class DeleteAnimationListener extends EndAnimationListener {
private final YourListObject obj;
public DeleteAnimationListener(YourListObject obj) {
this.obj = obj;
}
#Override
public void onAnimationEnd(Animation animation) {
yourObjects.remove(obj);
deleteItems.remove(obj);
notifyDataSetChanged();
}
};
currentObj is the view in the list your currently manipulating
yourObjects is the list you are storing the data in.
deleteItems is a new list.
You call .remove(obj) on your adapter to remove one.
The getView will then remove it from the list (with a fade out animation) once this animation is finished the AnimationListener will remove the actual object from your dataset.
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;
}
}
}
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