I am developing an application, and I need a ListView like conctact ListView of my Samsung Galaxy S:
When I slide my finger to the right I can send message to this contact.
When I slide my finger to the right I can call to my contact.
I have my ListView and only need the function for do it...
Thanks in advance.
PD:
I searched a lot and have not found anything. The most similar:
Resource for Android Slight Left/Right Slide action on listview
From another post, there was a link to this Google Code : https://gist.github.com/2980593
Which come from this Google+ post : https://plus.google.com/u/0/113735310430199015092/posts/Fgo1p5uWZLu .
This is a Swipe-To-Dismiss functionality.
From this you can provide your own Swipe-To-Action code. So here is my version, were I can personalize the left and right action and you can triggered the Dismiss animation (this is just a modification of Roman Nuric's code).
You have to include this class in your project :
public class SwipeListViewTouchListener implements View.OnTouchListener {
// Cached ViewConfiguration and system-wide constant values
private int mSlop;
private int mMinFlingVelocity;
private int mMaxFlingVelocity;
private long mAnimationTime;
// Fixed properties
private ListView mListView;
private OnSwipeCallback mCallback;
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
private boolean dismissLeft = true;
private boolean dismissRight = true;
// Transient properties
private List < PendingSwipeData > mPendingSwipes = new ArrayList < PendingSwipeData > ();
private int mDismissAnimationRefCount = 0;
private float mDownX;
private boolean mSwiping;
private VelocityTracker mVelocityTracker;
private int mDownPosition;
private View mDownView;
private boolean mPaused;
/**
* The callback interface used by {#link SwipeListViewTouchListener} to inform its client
* about a successful swipe of one or more list item positions.
*/
public interface OnSwipeCallback {
/**
* Called when the user has swiped the list item to the left.
*
* #param listView The originating {#link ListView}.
* #param reverseSortedPositions An array of positions to dismiss, sorted in descending
* order for convenience.
*/
void onSwipeLeft(ListView listView, int[] reverseSortedPositions);
void onSwipeRight(ListView listView, int[] reverseSortedPositions);
}
/**
* Constructs a new swipe-to-action touch listener for the given list view.
*
* #param listView The list view whose items should be dismissable.
* #param callback The callback to trigger when the user has indicated that she would like to
* dismiss one or more list items.
*/
public SwipeListViewTouchListener(ListView listView, OnSwipeCallback callback) {
ViewConfiguration vc = ViewConfiguration.get(listView.getContext());
mSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
mAnimationTime = listView.getContext().getResources().getInteger(
android.R.integer.config_shortAnimTime);
mListView = listView;
mCallback = callback;
}
/**
* Constructs a new swipe-to-action touch listener for the given list view.
*
* #param listView The list view whose items should be dismissable.
* #param callback The callback to trigger when the user has indicated that she would like to
* dismiss one or more list items.
* #param dismissLeft set if the dismiss animation is up when the user swipe to the left
* #param dismissRight set if the dismiss animation is up when the user swipe to the right
* #see #SwipeListViewTouchListener(ListView, OnSwipeCallback, boolean, boolean)
*/
public SwipeListViewTouchListener(ListView listView, OnSwipeCallback callback, boolean dismissLeft, boolean dismissRight) {
this(listView, callback);
this.dismissLeft = dismissLeft;
this.dismissRight = dismissRight;
}
/**
* Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
*
* #param enabled Whether or not to watch for gestures.
*/
public void setEnabled(boolean enabled) {
mPaused = !enabled;
}
/**
* Returns an {#link android.widget.AbsListView.OnScrollListener} to be added to the
* {#link ListView} using
* {#link ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}.
* If a scroll listener is already assigned, the caller should still pass scroll changes
* through to this listener. This will ensure that this
* {#link SwipeListViewTouchListener} is paused during list view scrolling.</p>
*
* #see {#link SwipeListViewTouchListener}
*/
public AbsListView.OnScrollListener makeScrollListener() {
return new AbsListView.OnScrollListener() {#
Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
}
#
Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {}
};
}
#
Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (mViewWidth < 2) {
mViewWidth = mListView.getWidth();
}
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
{
if (mPaused) {
return false;
}
// TODO: ensure this is a finger, and set a flag
// Find the child view that was touched (perform a hit test)
Rect rect = new Rect();
int childCount = mListView.getChildCount();
int[] listViewCoords = new int[2];
mListView.getLocationOnScreen(listViewCoords);
int x = (int) motionEvent.getRawX() - listViewCoords[0];
int y = (int) motionEvent.getRawY() - listViewCoords[1];
View child;
for (int i = 0; i < childCount; i++) {
child = mListView.getChildAt(i);
child.getHitRect(rect);
if (rect.contains(x, y)) {
mDownView = child;
break;
}
}
if (mDownView != null) {
mDownX = motionEvent.getRawX();
mDownPosition = mListView.getPositionForView(mDownView);
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(motionEvent);
}
view.onTouchEvent(motionEvent);
return true;
}
case MotionEvent.ACTION_UP:
{
if (mVelocityTracker == null) {
break;
}
float deltaX = motionEvent.getRawX() - mDownX;
mVelocityTracker.addMovement(motionEvent);
mVelocityTracker.computeCurrentVelocity(500); // 1000 by defaut but it was too much
float velocityX = Math.abs(mVelocityTracker.getXVelocity());
float velocityY = Math.abs(mVelocityTracker.getYVelocity());
boolean swipe = false;
boolean swipeRight = false;
if (Math.abs(deltaX) > mViewWidth / 2) {
swipe = true;
swipeRight = deltaX > 0;
} else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity && velocityY < velocityX) {
swipe = true;
swipeRight = mVelocityTracker.getXVelocity() > 0;
}
if (swipe) {
// sufficent swipe value
final View downView = mDownView; // mDownView gets null'd before animation ends
final int downPosition = mDownPosition;
final boolean toTheRight = swipeRight;
++mDismissAnimationRefCount;
mDownView.animate()
.translationX(swipeRight ? mViewWidth : -mViewWidth)
.alpha(0)
.setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {#
Override
public void onAnimationEnd(Animator animation) {
performSwipeAction(downView, downPosition, toTheRight, toTheRight ? dismissRight : dismissLeft);
}
});
} else {
// cancel
mDownView.animate()
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker = null;
mDownX = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}
case MotionEvent.ACTION_MOVE:
{
if (mVelocityTracker == null || mPaused) {
break;
}
mVelocityTracker.addMovement(motionEvent);
float deltaX = motionEvent.getRawX() - mDownX;
if (Math.abs(deltaX) > mSlop) {
mSwiping = true;
mListView.requestDisallowInterceptTouchEvent(true);
// Cancel ListView's touch (un-highlighting the item)
MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
(motionEvent.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
mListView.onTouchEvent(cancelEvent);
}
if (mSwiping) {
mDownView.setTranslationX(deltaX);
mDownView.setAlpha(Math.max(0f, Math.min(1f,
1f - 2f * Math.abs(deltaX) / mViewWidth)));
return true;
}
break;
}
}
return false;
}
class PendingSwipeData implements Comparable < PendingSwipeData > {
public int position;
public View view;
public PendingSwipeData(int position, View view) {
this.position = position;
this.view = view;
}
#
Override
public int compareTo(PendingSwipeData other) {
// Sort by descending position
return other.position - position;
}
}
private void performSwipeAction(final View swipeView, final int swipePosition, boolean toTheRight, boolean dismiss) {
// Animate the dismissed list item to zero-height and fire the dismiss callback when
// all dismissed list item animations have completed. This triggers layout on each animation
// frame; in the future we may want to do something smarter and more performant.
final ViewGroup.LayoutParams lp = swipeView.getLayoutParams();
final int originalHeight = swipeView.getHeight();
final boolean swipeRight = toTheRight;
ValueAnimator animator;
if (dismiss)
animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
else
animator = ValueAnimator.ofInt(originalHeight, originalHeight - 1).setDuration(mAnimationTime);
animator.addListener(new AnimatorListenerAdapter() {#
Override
public void onAnimationEnd(Animator animation) {
--mDismissAnimationRefCount;
if (mDismissAnimationRefCount == 0) {
// No active animations, process all pending dismisses.
// Sort by descending position
Collections.sort(mPendingSwipes);
int[] swipePositions = new int[mPendingSwipes.size()];
for (int i = mPendingSwipes.size() - 1; i >= 0; i--) {
swipePositions[i] = mPendingSwipes.get(i).position;
}
if (swipeRight)
mCallback.onSwipeRight(mListView, swipePositions);
else
mCallback.onSwipeLeft(mListView, swipePositions);
ViewGroup.LayoutParams lp;
for (PendingSwipeData pendingDismiss: mPendingSwipes) {
// Reset view presentation
pendingDismiss.view.setAlpha(1f);
pendingDismiss.view.setTranslationX(0);
lp = pendingDismiss.view.getLayoutParams();
lp.height = originalHeight;
pendingDismiss.view.setLayoutParams(lp);
}
mPendingSwipes.clear();
}
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {#
Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
lp.height = (Integer) valueAnimator.getAnimatedValue();
swipeView.setLayoutParams(lp);
}
});
mPendingSwipes.add(new PendingSwipeData(swipePosition, swipeView));
animator.start();
}
}
From there, you can add the following code to your onCreate in the Activity with the ListView :
// Create a ListView-specific touch listener. ListViews are given special treatment because
// by default they handle touches for their list items... i.e. they're in charge of drawing
// the pressed state (the list selector), handling list item clicks, etc.
SwipeListViewTouchListener touchListener =
new SwipeListViewTouchListener(
listView,
new SwipeListViewTouchListener.OnSwipeCallback() {
#Override
public void onSwipeLeft(ListView listView, int [] reverseSortedPositions) {
// Log.i(this.getClass().getName(), "swipe left : pos="+reverseSortedPositions[0]);
// TODO : YOUR CODE HERE FOR LEFT ACTION
}
#Override
public void onSwipeRight(ListView listView, int [] reverseSortedPositions) {
// Log.i(ProfileMenuActivity.class.getClass().getName(), "swipe right : pos="+reverseSortedPositions[0]);
// TODO : YOUR CODE HERE FOR RIGHT ACTION
}
},
true, // example : left action = dismiss
false); // example : right action without dismiss animation
listView.setOnTouchListener(touchListener);
// Setting this scroll listener is required to ensure that during ListView scrolling,
// we don't look for swipes.
listView.setOnScrollListener(touchListener.makeScrollListener());
Edit:
To add a color modification while swiping, your code must be in the case MotionEvent.ACTION_MOVE close to the mDownView.setAlpha.
What you might what to do here is create a new view especially for the list view (call it ListViewFlinger or something). Then in this view, override its onTouchEvent method and place some code in there to determine a slide gesture. Once you have the slide gesture, fire a onSlideComplete event (you'll have to make that listener) an voialla, you a ListView with slide activated content.
float historicX = Float.NaN, historicY = Float.NaN;
static final TRIGGER_DELTA = 50; // Number of pixels to travel till trigger
#Override public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
historicX = e.getX();
historicY = e.getY();
break;
case MotionEvent.ACTION_UP:
if (e.getX() - historicX > -TRIGGER_DELTA) {
onSlideComplete(Direction.LEFT);
return true;
}
else if (e.getX() - historicX > TRIGGER_DELTA) {
onSlideComplete(Direction.RIGHT);
return true;
} break;
default:
return super.onTouchEvent(e);
}
}
enum Direction {
LEFT, RIGHT;
}
interface OnSlideCompleteListener {
void onSlideComplete(Direction dir);
}
If you want to Perform an action On Swiping:
Check out SwipeActionAdapter
It's an awesome library that allows Swipe in both directions with an underlying Layout or Color, and performs a desired action when the swipe/slide gesture is done. You can configure it to reveal/change the layout.
~ I haven't used the Samsung Contacts app, but sounds like this is what you want
If you want to swipe to Reveal actionable buttons:
Check out SwipeMenuListView
In a sense, it is more like the Swipe-able TableViews in iOS.
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.
I am developing an Android app. In my app, I am using Recycler View. This is my very first time of using Recycler View. I using it together with CardView. The reason why I am using recycler view is I want to remove row by swiping left or right. But I cannot find event to do it in Recycler View. How can I set it? Is it built-in included in Recycler View?
I installed using grandle like this
compile 'com.android.support:cardview-v7:23.0.+'
compile 'com.android.support:recyclerview-v7:23.0.+'
This is my adapter
public class CardAdapter extends RecyclerView.Adapter<CardAdapter.MyViewHolder> {
private ArrayList<CardData> values;
public CardAdapter(ArrayList<CardData> valuesPram)
{
this.values = valuesPram;
}
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView name,email;
public MyViewHolder(View view) {
super(view);
name = (TextView) view.findViewById(R.id.card_name);
email = (TextView)view.findViewById(R.id.card_email);
}
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.cards_layout, parent, false);
return new MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
CardData cardData = values.get(position);
holder.email.setText(cardData.getEmail());
holder.name.setText(cardData.getName());
}
#Override
public int getItemCount() {
return values.size();
}
}
This is my row layout
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardBackgroundColor="#color/colorAccent"
card_view:cardCornerRadius="10dp"
card_view:cardElevation="5dp"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="#+id/card_name"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/card_email"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</android.support.v7.widget.CardView>
This is my activity
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private RecyclerView.LayoutManager layoutManager;
private ArrayList<Integer> removedItems;
private ArrayList<CardData> listItems;
private CardAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initializeData();
setUpRecyclerView();
}
private void setUpRecyclerView()
{
recyclerView = (RecyclerView)findViewById(R.id.my_recycler_view);
recyclerView.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(adapter);
}
private void initializeData()
{
listItems = new ArrayList<CardData>();
CardData item1 = new CardData();
item1.setName("Name 1");
item1.setEmail("Email 1");
listItems.add(item1);
CardData item2 = new CardData();
item2.setName("Name 2");
item2.setEmail("Email 2");
listItems.add(item2);
CardData item3 = new CardData();
item3.setName("Name 3");
item3.setEmail("Email 3");
listItems.add(item3);
CardData item4 = new CardData();
item4.setName("Name 4");
item4.setEmail("Email 4");
listItems.add(item4);
adapter = new CardAdapter(listItems);
}
private void removeItem(View v)
{
int selectedItemPosition = recyclerView.getChildPosition(v);
RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForPosition(selectedItemPosition);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
My code is working fine. My question is how can I implement event for removing item by swiping left or right to one of the rows of recycler view?
A simple solution is to use ItemTouchHelper provided by RecyclerView itself
//Swipe to Delete
ItemTouchHelper swipeToDismissTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction)
{
// Do Stuff
}
});
swipeToDismissTouchHelper.attachToRecyclerView(recyclerView);
There's small piece of code named ItemTouchHelper developed by #iPaulPro. It works perfect and supports dragging and swiping of virtually any RecyclerView. All you need is to implement 3 simple interfaces + slightly rewrite callback class (in simple cases works provided callback class).
Here's a link in github
Add this class to your project SwipeDismissRecyclerViewTouchListener add the following line in your activity
SwipeDismissRecyclerViewTouchListener touchListener =
new SwipeDismissRecyclerViewTouchListener(
recyclerObject,
new SwipeDismissRecyclerViewTouchListener.DismissCallbacks() {
#Override
public boolean canDismiss(int position) {
return true;
}
#Override
public void onDismiss(RecyclerView recyclerView, int[] reverseSortedPositions) {
for (final int position : reverseSortedPositions) {
your_array.remove(position);
}
// do not call notifyItemRemoved for every item, it will cause gaps on deleting items
adapter.notifyDataSetChanged();
}
});
recyclerObject.setOnTouchListener(touchListener);
SwipeDismissRecyclerViewTouchListener
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.os.SystemClock;
import android.support.v7.widget.RecyclerView;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SwipeDismissRecyclerViewTouchListener implements View.OnTouchListener {
// Cached ViewConfiguration and system-wide constant values
private int mSlop;
private int mMinFlingVelocity;
private int mMaxFlingVelocity;
private long mAnimationTime;
// Fixed properties
private RecyclerView mRecyclerView;
private DismissCallbacks mCallbacks;
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
// Transient properties
private List<PendingDismissData> mPendingDismisses = new ArrayList<PendingDismissData>();
private int mDismissAnimationRefCount = 0;
private float mDownX;
private float mDownY;
private boolean mSwiping;
private int mSwipingSlop;
private VelocityTracker mVelocityTracker;
private int mDownPosition;
private View mDownView;
private boolean mPaused;
/**
* The callback interface used by {#link coverflow.example.bala.myrecyclerview.SwipeDismissRecyclerViewTouchListener} to inform its client
* about a successful dismissal of one or more list item positions.
*/
public interface DismissCallbacks {
/**
* Called to determine whether the given position can be dismissed.
*/
boolean canDismiss(int position);
/**
* Called when the user has indicated they she would like to dismiss one or more list item
* positions.
*
* #param recyclerView The originating {#link ListView}.
* #param reverseSortedPositions An array of positions to dismiss, sorted in descending
* order for convenience.
*/
void onDismiss(RecyclerView recyclerView, int[] reverseSortedPositions);
}
/**
* Constructs a new swipe-to-dismiss touch listener for the given list view.
*
* #param recyclerView The list view whose items should be dismissable.
* #param callbacks The callback to trigger when the user has indicated that she would like to
* dismiss one or more list items.
*/
public SwipeDismissRecyclerViewTouchListener(RecyclerView recyclerView, DismissCallbacks callbacks) {
ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext());
mSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
mAnimationTime = recyclerView.getContext().getResources().getInteger(
android.R.integer.config_shortAnimTime);
mRecyclerView = recyclerView;
mCallbacks = callbacks;
}
/**
* Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
*
* #param enabled Whether or not to watch for gestures.
*/
public void setEnabled(boolean enabled) {
mPaused = !enabled;
}
/**
* Returns an {#link AbsListView.OnScrollListener} to be added to the {#link
* ListView} using {#link ListView#setOnScrollListener(AbsListView.OnScrollListener)}.
* If a scroll listener is already assigned, the caller should still pass scroll changes through
* to this listener. This will ensure that this {#link coverflow.example.bala.myrecyclerview.SwipeDismissRecyclerViewTouchListener} is
* paused during list view scrolling.</p>
*
* #see coverflow.example.bala.myrecyclerview.SwipeDismissRecyclerViewTouchListener
*/
public RecyclerView.OnScrollListener makeScrollListener() {
return new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
setEnabled(newState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
}
};
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (mViewWidth < 2) {
mViewWidth = mRecyclerView.getWidth();
}
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
if (mPaused) {
return false;
}
// TODO: ensure this is a finger, and set a flag
// Find the child view that was touched (perform a hit test)
Rect rect = new Rect();
int childCount = mRecyclerView.getChildCount();
int[] listViewCoords = new int[2];
mRecyclerView.getLocationOnScreen(listViewCoords);
int x = (int) motionEvent.getRawX() - listViewCoords[0];
int y = (int) motionEvent.getRawY() - listViewCoords[1];
View child;
for (int i = 0; i < childCount; i++) {
child = mRecyclerView.getChildAt(i);
child.getHitRect(rect);
if (rect.contains(x, y)) {
mDownView = child;
break;
}
}
if (mDownView != null) {
mDownX = motionEvent.getRawX();
mDownY = motionEvent.getRawY();
mDownPosition = mRecyclerView.getChildPosition(mDownView);
if (mCallbacks.canDismiss(mDownPosition)) {
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(motionEvent);
} else {
mDownView = null;
}
}
return false;
}
case MotionEvent.ACTION_CANCEL: {
if (mVelocityTracker == null) {
break;
}
if (mDownView != null && mSwiping) {
// cancel
mDownView.animate()
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}
case MotionEvent.ACTION_UP: {
if (mVelocityTracker == null) {
break;
}
float deltaX = motionEvent.getRawX() - mDownX;
mVelocityTracker.addMovement(motionEvent);
mVelocityTracker.computeCurrentVelocity(1000);
float velocityX = mVelocityTracker.getXVelocity();
float absVelocityX = Math.abs(velocityX);
float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
boolean dismiss = false;
boolean dismissRight = false;
if (Math.abs(deltaX) > mViewWidth / 2 && mSwiping) {
dismiss = true;
dismissRight = deltaX > 0;
} else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
&& absVelocityY < absVelocityX && mSwiping) {
// dismiss only if flinging in the same direction as dragging
dismiss = (velocityX < 0) == (deltaX < 0);
dismissRight = mVelocityTracker.getXVelocity() > 0;
}
if (dismiss && mDownPosition != ListView.INVALID_POSITION) {
// dismiss
final View downView = mDownView; // mDownView gets null'd before animation ends
final int downPosition = mDownPosition;
++mDismissAnimationRefCount;
mDownView.animate()
.translationX(dismissRight ? mViewWidth : -mViewWidth)
.alpha(0)
.setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
performDismiss(downView, downPosition);
}
});
} else {
// cancel
mDownView.animate()
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}
case MotionEvent.ACTION_MOVE: {
if (mVelocityTracker == null || mPaused) {
break;
}
mVelocityTracker.addMovement(motionEvent);
float deltaX = motionEvent.getRawX() - mDownX;
float deltaY = motionEvent.getRawY() - mDownY;
if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) {
mSwiping = true;
mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
mRecyclerView.requestDisallowInterceptTouchEvent(true);
// Cancel ListView's touch (un-highlighting the item)
MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
(motionEvent.getActionIndex()
<< MotionEvent.ACTION_POINTER_INDEX_SHIFT));
mRecyclerView.onTouchEvent(cancelEvent);
cancelEvent.recycle();
}
if (mSwiping) {
mDownView.setTranslationX(deltaX - mSwipingSlop);
mDownView.setAlpha(Math.max(0f, Math.min(1f,
1f - 2f * Math.abs(deltaX) / mViewWidth)));
return true;
}
break;
}
}
return false;
}
class PendingDismissData implements Comparable<PendingDismissData> {
public int position;
public View view;
public PendingDismissData(int position, View view) {
this.position = position;
this.view = view;
}
#Override
public int compareTo(PendingDismissData other) {
// Sort by descending position
return other.position - position;
}
}
private void performDismiss(final View dismissView, final int dismissPosition) {
// Animate the dismissed list item to zero-height and fire the dismiss callback when
// all dismissed list item animations have completed. This triggers layout on each animation
// frame; in the future we may want to do something smarter and more performant.
final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
final int originalHeight = dismissView.getHeight();
ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
animator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
--mDismissAnimationRefCount;
if (mDismissAnimationRefCount == 0) {
// No active animations, process all pending dismisses.
// Sort by descending position
Collections.sort(mPendingDismisses);
int[] dismissPositions = new int[mPendingDismisses.size()];
for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
dismissPositions[i] = mPendingDismisses.get(i).position;
}
mCallbacks.onDismiss(mRecyclerView, dismissPositions);
// Reset mDownPosition to avoid MotionEvent.ACTION_UP trying to start a dismiss
// animation with a stale position
mDownPosition = ListView.INVALID_POSITION;
ViewGroup.LayoutParams lp;
for (PendingDismissData pendingDismiss : mPendingDismisses) {
// Reset view presentation
pendingDismiss.view.setAlpha(1f);
pendingDismiss.view.setTranslationX(0);
lp = pendingDismiss.view.getLayoutParams();
lp.height = originalHeight;
pendingDismiss.view.setLayoutParams(lp);
}
// Send a cancel event
long time = SystemClock.uptimeMillis();
MotionEvent cancelEvent = MotionEvent.obtain(time, time,
MotionEvent.ACTION_CANCEL, 0, 0, 0);
mRecyclerView.dispatchTouchEvent(cancelEvent);
mPendingDismisses.clear();
}
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
lp.height = (Integer) valueAnimator.getAnimatedValue();
dismissView.setLayoutParams(lp);
}
});
mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView));
animator.start();
}
}
i think this would help you
val simpleCallback: ItemTouchHelper.SimpleCallback =
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.adapterPosition //get position which is swipe
if (direction == ItemTouchHelper.LEFT) { //if swipe left
val builder: AlertDialog.Builder =
AlertDialog.Builder(this#Activity) //alert for confirm to delete
builder.setMessage("Are you sure to delete?") //set message
builder.setPositiveButton("REMOVE",
DialogInterface.OnClickListener { dialog, which ->
return#OnClickListener
}).setNegativeButton("CANCEL",
DialogInterface.OnClickListener { dialog, which ->
return#OnClickListener
}).show()
}
}
}
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(binding.recycler)
i want swipe list view items left or right and after swipe items are deleted
`
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
items = new ArrayList<String>();
items.add("arpit");
items.add("b");
items.add("c");
items.add("d");
items.add("e");
items.add("f");
items.add("g");
items.add("h");
items.add("i");
items.add("j");
items.add("k");
items.add("l");
//String[] items = { "arpit", "avninash", "lucky", "rakesh", "jitendra", "arun", "dharmendra", "amitabh", "arpit", "avninash", "lucky", "rakesh", "jitendra", "arun", "dharmendra", "amitabh" };
listview = (ListView) findViewById(R.id.listView1);
arrayadapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items);
listview.setAdapter(arrayadapter);
listview.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(getApplicationContext(), "plese Move me", Toast.LENGTH_SHORT).show();
arrayadapter.remove(arrayadapter.getItem(position));
arrayadapter.notifyDataSetChanged();
}
});
}
}
`
iam using this code this code creating a list view, and after click item delete item but not swipe .
SwipeListViewTouchListener.java
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SwipeListViewTouchListener implements View.OnTouchListener {
private int mSlop;
private int mMinFlingVelocity;
private int mMaxFlingVelocity;
private long mAnimationTime;
// Fixed properties
private ListView mListView;
private OnSwipeCallback mCallback;
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
private boolean dismissLeft = true;
private boolean dismissRight = true;
// Transient properties
private List< PendingSwipeData > mPendingSwipes = new ArrayList< PendingSwipeData >();
private int mDismissAnimationRefCount = 0;
private float mDownX;
private boolean mSwiping;
private VelocityTracker mVelocityTracker;
private int mDownPosition;
private View mDownView;
private boolean mPaused;
/**
* The callback interface used by {#link SwipeListViewTouchListener} to inform its client
* about a successful swipe of one or more list item positions.
*/
public interface OnSwipeCallback {
/**
* Called when the user has swiped the list item to the left.
*
* #param listView The originating {#link ListView}.
* #param reverseSortedPositions An array of positions to dismiss, sorted in descending
* order for convenience.
*/
void onSwipeLeft(ListView listView, int[] reverseSortedPositions);
void onSwipeRight(ListView listView, int[] reverseSortedPositions);
}
/**
* Constructs a new swipe-to-action touch listener for the given list view.
*
* #param listView The list view whose items should be dismissable.
* #param callback The callback to trigger when the user has indicated that she would like to
* dismiss one or more list items.
*/
public SwipeListViewTouchListener(ListView listView, OnSwipeCallback callback) {
ViewConfiguration vc = ViewConfiguration.get(listView.getContext());
mSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
mAnimationTime = listView.getContext().getResources().getInteger(
android.R.integer.config_shortAnimTime);
mListView = listView;
mCallback = callback;
}
/**
* Constructs a new swipe-to-action touch listener for the given list view.
*
* #param listView The list view whose items should be dismissable.
* #param callback The callback to trigger when the user has indicated that she would like to
* dismiss one or more list items.
* #param dismissLeft set if the dismiss animation is up when the user swipe to the left
* #param dismissRight set if the dismiss animation is up when the user swipe to the right
* #see #SwipeListViewTouchListener(ListView, OnSwipeCallback, boolean, boolean)
*/
public SwipeListViewTouchListener(ListView listView, OnSwipeCallback callback, boolean dismissLeft, boolean dismissRight) {
this(listView, callback);
this.dismissLeft = dismissLeft;
this.dismissRight = dismissRight;
}
/**
* Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
*
* #param enabled Whether or not to watch for gestures.
*/
public void setEnabled(boolean enabled) {
mPaused = !enabled;
}
/**
* Returns an {#link android.widget.AbsListView.OnScrollListener} to be added to the
* {#link ListView} using
* {#link ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}.
* If a scroll listener is already assigned, the caller should still pass scroll changes
* through to this listener. This will ensure that this
* {#link SwipeListViewTouchListener} is paused during list view scrolling.</p>
*
* #see {#link SwipeListViewTouchListener}
*/
public AbsListView.OnScrollListener makeScrollListener() {
return new AbsListView.OnScrollListener() {#
Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
}
#
Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {}
};
}
#
Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (mViewWidth < 2) {
mViewWidth = mListView.getWidth();
}
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
{
if (mPaused) {
return false;
}
// TODO: ensure this is a finger, and set a flag
// Find the child view that was touched (perform a hit test)
Rect rect = new Rect();
int childCount = mListView.getChildCount();
int[] listViewCoords = new int[2];
mListView.getLocationOnScreen(listViewCoords);
int x = (int) motionEvent.getRawX() - listViewCoords[0];
int y = (int) motionEvent.getRawY() - listViewCoords[1];
View child;
for (int i = 0; i < childCount; i++) {
child = mListView.getChildAt(i);
child.getHitRect(rect);
if (rect.contains(x, y)) {
mDownView = child;
break;
}
}
if (mDownView != null) {
mDownX = motionEvent.getRawX();
mDownPosition = mListView.getPositionForView(mDownView);
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(motionEvent);
}
view.onTouchEvent(motionEvent);
return true;
}
case MotionEvent.ACTION_UP:
{
if (mVelocityTracker == null) {
break;
}
float deltaX = motionEvent.getRawX() - mDownX;
mVelocityTracker.addMovement(motionEvent);
mVelocityTracker.computeCurrentVelocity(500); // 1000 by defaut but it was too much
float velocityX = Math.abs(mVelocityTracker.getXVelocity());
float velocityY = Math.abs(mVelocityTracker.getYVelocity());
boolean swipe = false;
boolean swipeRight = false;
if (Math.abs(deltaX) > mViewWidth / 2) {
swipe = true;
swipeRight = deltaX > 0;
} else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity && velocityY < velocityX) {
swipe = true;
swipeRight = mVelocityTracker.getXVelocity() > 0;
}
if (swipe) {
// sufficent swipe value
final View downView = mDownView; // mDownView gets null'd before animation ends
final int downPosition = mDownPosition;
final boolean toTheRight = swipeRight;
++mDismissAnimationRefCount;
mDownView.animate()
.translationX(swipeRight ? mViewWidth : -mViewWidth)
.alpha(0)
.setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {#
Override
public void onAnimationEnd(Animator animation) {
performSwipeAction(downView, downPosition, toTheRight, toTheRight ? dismissRight : dismissLeft);
}
});
} else {
// cancel
mDownView.animate()
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker = null;
mDownX = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}
case MotionEvent.ACTION_MOVE:
{
if (mVelocityTracker == null || mPaused) {
break;
}
mVelocityTracker.addMovement(motionEvent);
float deltaX = motionEvent.getRawX() - mDownX;
if (Math.abs(deltaX) > mSlop) {
mSwiping = true;
mListView.requestDisallowInterceptTouchEvent(true);
// Cancel ListView's touch (un-highlighting the item)
MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
(motionEvent.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
mListView.onTouchEvent(cancelEvent);
}
if (mSwiping) {
mDownView.setTranslationX(deltaX);
mDownView.setAlpha(Math.max(0f, Math.min(1f,
1f - 2f * Math.abs(deltaX) / mViewWidth)));
return true;
}
break;
}
}
return false;
}
class PendingSwipeData implements Comparable < PendingSwipeData > {
public int position;
public View view;
public PendingSwipeData(int position, View view) {
this.position = position;
this.view = view;
}
#
Override
public int compareTo(PendingSwipeData other) {
// Sort by descending position
return other.position - position;
}
}
private void performSwipeAction(final View swipeView, final int swipePosition, boolean toTheRight, boolean dismiss) {
// Animate the dismissed list item to zero-height and fire the dismiss callback when
// all dismissed list item animations have completed. This triggers layout on each animation
// frame; in the future we may want to do something smarter and more performant.
final ViewGroup.LayoutParams lp = swipeView.getLayoutParams();
final int originalHeight = swipeView.getHeight();
final boolean swipeRight = toTheRight;
ValueAnimator animator;
if (dismiss)
animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
else
animator = ValueAnimator.ofInt(originalHeight, originalHeight - 1).setDuration(mAnimationTime);
animator.addListener(new AnimatorListenerAdapter() {#
Override
public void onAnimationEnd(Animator animation) {
--mDismissAnimationRefCount;
if (mDismissAnimationRefCount == 0) {
// No active animations, process all pending dismisses.
// Sort by descending position
Collections.sort(mPendingSwipes);
int[] swipePositions = new int[mPendingSwipes.size()];
for (int i = mPendingSwipes.size() - 1; i >= 0; i--) {
swipePositions[i] = mPendingSwipes.get(i).position;
}
if (swipeRight)
mCallback.onSwipeRight(mListView, swipePositions);
else
mCallback.onSwipeLeft(mListView, swipePositions);
ViewGroup.LayoutParams lp;
for (PendingSwipeData pendingDismiss: mPendingSwipes) {
// Reset view presentation
pendingDismiss.view.setAlpha(1f);
pendingDismiss.view.setTranslationX(0);
lp = pendingDismiss.view.getLayoutParams();
lp.height = originalHeight;
pendingDismiss.view.setLayoutParams(lp);
}
mPendingSwipes.clear();
}
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {#
Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
lp.height = (Integer) valueAnimator.getAnimatedValue();
swipeView.setLayoutParams(lp);
}
});
mPendingSwipes.add(new PendingSwipeData(swipePosition, swipeView));
animator.start();
}
}
Usage
// Create a ListView-specific touch listener. ListViews are given special treatment because
// by default they handle touches for their list items... i.e. they're in charge of drawing
// the pressed state (the list selector), handling list item clicks, etc.
SwipeListViewTouchListener touchListener = new SwipeListViewTouchListener(
listView,
new SwipeListViewTouchListener.OnSwipeCallback() {
#Override
public void onSwipeLeft(
ListView listView, int[] reverseSortedPositions)
{
//onLeftSwipe
}
#Override
public void onSwipeRight(ListView listView, int[] reverseSortedPositions)
{
//onRightSwipe
}
},true, // example : left action = dismiss
false); // example : right action without dismiss animation
listView.setOnTouchListener(touchListener);
// Setting this scroll listener is required to ensure that during ListView scrolling,
// we don't look for swipes.
listView.setOnScrollListener(touchListener.makeScrollListener());
well that's normal because you use the onClick method.
If you want to remove on swipe, you will have to implement the OnSwipeTouchListener :
Android: How to handle right to left swipe gestures
Define a ViewPager in your layout .xml:
<android.support.v4.view.ViewPager
android:id="#+id/example_pager"
android:layout_width="fill_parent"
android:layout_height="#dimen/abc_action_bar_default_height" />
And then, in your activity / fragment, set a custom pager adapter:
In an activity:
protected void onCreate(Bundle savedInstanceState) {
PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager());
ViewPager pager = (ViewPager) findViewById(R.id.example_pager);
pager.setAdapter(adapter);
// pager.setOnPageChangeListener(this); // You can set a page listener here
pager.setCurrentItem(0);
}
In a fragment:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_layout, container, false);
if (view != null) {
PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager());
ViewPager pager = (ViewPager) view.findViewById(R.id.example_pager);
pager.setAdapter(adapter);
// pager.setOnPageChangeListener(this); // You can set a page listener here
pager.setCurrentItem(0);
}
return view;
}
Create our custom pager class:
// setup your PagerAdapter which extends FragmentPagerAdapter
class PagerAdapter extends FragmentPagerAdapter {
public static final int NUM_PAGES = 2;
private CustomFragment[] mFragments = new CustomFragment[NUM_PAGES];
public PagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
# Override
public int getCount() {
return NUM_PAGES;
}
# Override
public Fragment getItem(int position) {
if (mFragments[position] == null) {
// this calls the newInstance from when you setup the ListFragment
mFragments[position] = new CustomFragment();
}
return mFragments[position];
}
}
I have an activity that gets a bitmap from the sdcard. Its view is set to a custom viewgroup. i have dynamically added a view containing the bitmap to the viewgroup and used a handler to update the viewgroup in the UI thread, but to no avail. The viewgroup doesn't show the bitmap. The viewgroup code I have not written myself but ought to allow the user to swipe the screen to load different views/bitmaps.
i have checked to see if the bitmap is not null and placed log statements in various places in both files. Any ideas as to why the viewgroup's children are not being drawn?
The activity
public class HorizontalPagerActivity extends Activity {
private static final String TAG = "*********hpActivity";
private Context mContext = this;
File tempFile;
byte [] imageArray;
private Bitmap b = null;
private Handler handler;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.hpview);
final ViewGroup viewgroup = (ViewGroup)findViewById(R.id.hpview);
handler = new Handler();
tempFile = new File(Environment.getExternalStorageDirectory().
getAbsolutePath() + "/"+"image.jpeg");
Log.e(TAG, "image length = "+tempFile.length());
imageArray = new byte[(int)tempFile.length()];
try{
InputStream is = new FileInputStream(tempFile);
BufferedInputStream bis = new BufferedInputStream(is);
DataInputStream dis = new DataInputStream(bis);
int i = 0;
while (dis.available() > 0 ) {
imageArray[i] = dis.readByte();
i++;
}
dis.close();
} catch (Exception e) {
e.printStackTrace();
}
Bitmap bm = BitmapFactory.decodeByteArray(imageArray, 0, imageArray.length);
// Bitmap b = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888);
b = bm.copy(bm.getConfig(), true);
if(b == null){
Log.e(TAG, "b = null");
}else{
Log.e(TAG, "b = not null");
}
Canvas canvas = new Canvas(b);
Log.e(TAG, "canvas created");
final View view = new View(this);
Log.e(TAG, "view created");
// LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
// view.setLayoutParams(lp);
view.draw(canvas);
viewgroup.addView(view);
Log.e(TAG, "view added to viewgroup");
Runnable runnable = new Runnable() {
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
Log.e(TAG, "about to inval viewgroup");
viewgroup.invalidate();
Log.e(TAG, "finished inval viewgroup");
}
});
}
};
new Thread(runnable).start();
Log.e(TAG, "finished handler");
/*
runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
Log.e(TAG, "about to inval viewgroup");
viewgroup.postInvalidate();
Log.e(TAG, "finished inval viewgroup");
}
});
*/
Log.e(TAG, "no of chidren = "+viewgroup.getChildCount());
}
}
The viewgroup
/**
* A view group that allows users to switch between multiple screens (layouts) in the same way as
* the Android home screen (Launcher application).
* <p>
* You can add and remove views using the normal methods {#link ViewGroup#addView(View)},
* {#link ViewGroup#removeView(View)} etc. You may want to listen for updates by calling
* {#link HorizontalPager#setOnScreenSwitchListener(OnScreenSwitchListener)} in order to perform
* operations once a new screen has been selected.
*
* Modifications from original version (ysamlan): Animate argument in setCurrentScreen and duration
* in snapToScreen; onInterceptTouchEvent handling to support nesting a vertical Scrollview inside
* the RealViewSwitcher; allowing snapping to a view even during an ongoing scroll; snap to
* next/prev view on 25% scroll change; density-independent swipe sensitivity; width-independent
* pager animation durations on scrolling to properly handle large screens without excessively
* long animations.
*
* Other modifications:
* (aveyD) Handle orientation changes properly and fully snap to the right position.
*
* #author Marc Reichelt, http://www.marcreichelt.de/
* #version 0.1.0
*/
public final class HorizontalPager extends ViewGroup {
/*
* How long to animate between screens when programmatically setting with setCurrentScreen using
* the animate parameter
*/
private static final int ANIMATION_SCREEN_SET_DURATION_MILLIS = 500;
// What fraction (1/x) of the screen the user must swipe to indicate a page change
private static final int FRACTION_OF_SCREEN_WIDTH_FOR_SWIPE = 4;
private static final int INVALID_SCREEN = -1;
/*
* Velocity of a swipe (in density-independent pixels per second) to force a swipe to the
* next/previous screen. Adjusted into mDensityAdjustedSnapVelocity on init.
*/
private static final int SNAP_VELOCITY_DIP_PER_SECOND = 600;
// Argument to getVelocity for units to give pixels per second (1 = pixels per millisecond).
private static final int VELOCITY_UNIT_PIXELS_PER_SECOND = 1000;
private static final int TOUCH_STATE_REST = 0;
private static final int TOUCH_STATE_HORIZONTAL_SCROLLING = 1;
private static final int TOUCH_STATE_VERTICAL_SCROLLING = -1;
private int mCurrentScreen;
private int mDensityAdjustedSnapVelocity;
private boolean mFirstLayout = true;
private float mLastMotionX;
private float mLastMotionY;
private OnScreenSwitchListener mOnScreenSwitchListener;
private int mMaximumVelocity;
private int mNextScreen = INVALID_SCREEN;
private Scroller mScroller;
private int mTouchSlop;
private int mTouchState = TOUCH_STATE_REST;
private VelocityTracker mVelocityTracker;
private int mLastSeenLayoutWidth = -1;
private static final String TAG = "*********horizontalpager";
private Bitmap bm = null;
/**
* Simple constructor to use when creating a view from code.
*
* #param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
*/
public HorizontalPager(final Context context) {
super(context);
Log.e(TAG, "inside hp standard constructor");
init();
}
/**
* Constructor that is called when inflating a view from XML. This is called
* when a view is being constructed from an XML file, supplying attributes
* that were specified in the XML file. This version uses a default style of
* 0, so the only attribute values applied are those in the Context's Theme
* and the given AttributeSet.
*
* <p>
* The method onFinishInflate() will be called after all children have been
* added.
*
* #param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* #param attrs The attributes of the XML tag that is inflating the view.
* #see #View(Context, AttributeSet, int)
*/
public HorizontalPager(final Context context, final AttributeSet attrs) {
super(context, attrs);
Log.e(TAG, "inside hp constructor for xml inflation");
init();
}
/**
* Sets up the scroller and touch/fling sensitivity parameters for the pager.
*/
private void init() {
mScroller = new Scroller(getContext());
// Calculate the density-dependent snap velocity in pixels
DisplayMetrics displayMetrics = new DisplayMetrics();
((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()
.getMetrics(displayMetrics);
mDensityAdjustedSnapVelocity =
(int) (displayMetrics.density * SNAP_VELOCITY_DIP_PER_SECOND);
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("ViewSwitcher can only be used in EXACTLY mode.");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("ViewSwitcher 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++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
if (mFirstLayout) {
scrollTo(mCurrentScreen * width, 0);
mFirstLayout = false;
}
else if (width != mLastSeenLayoutWidth) { // Width has changed
/*
* Recalculate the width and scroll to the right position to be sure we're in the right
* place in the event that we had a rotation that didn't result in an activity restart
* (code by aveyD). Without this you can end up between two pages after a rotation.
*/
Display display =
((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
int displayWidth = display.getWidth();
mNextScreen = Math.max(0, Math.min(getCurrentScreen(), getChildCount() - 1));
final int newX = mNextScreen * displayWidth;
final int delta = newX - getScrollX();
mScroller.startScroll(getScrollX(), 0, delta, 0, 0);
}
mLastSeenLayoutWidth = width;
}
#Override
protected void onLayout(final boolean changed, final int l, final int t, final int r,
final int b) {
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;
}
}
}
#Override
public boolean onInterceptTouchEvent(final MotionEvent ev) {
/*
* By Yoni Samlan: Modified onInterceptTouchEvent based on standard ScrollView's
* onIntercept. The logic is designed to support a nested vertically scrolling view inside
* this one; once a scroll registers for X-wise scrolling, handle it in this view and don't
* let the children, but once a scroll registers for y-wise scrolling, let the children
* handle it exclusively.
*/
final int action = ev.getAction();
boolean intercept = false;
switch (action) {
case MotionEvent.ACTION_MOVE:
/*
* If we're in a horizontal scroll event, take it (intercept further events). But if
* we're mid-vertical-scroll, don't even try; let the children deal with it. If we
* haven't found a scroll event yet, check for one.
*/
if (mTouchState == TOUCH_STATE_HORIZONTAL_SCROLLING) {
/*
* We've already started a horizontal scroll; set intercept to true so we can
* take the remainder of all touch events in onTouchEvent.
*/
intercept = true;
} else if (mTouchState == TOUCH_STATE_VERTICAL_SCROLLING) {
// Let children handle the events for the duration of the scroll event.
intercept = false;
} else { // We haven't picked up a scroll event yet; check for one.
/*
* If we detected a horizontal scroll event, start stealing touch events (mark
* as scrolling). Otherwise, see if we had a vertical scroll event -- if so, let
* the children handle it and don't look to intercept again until the motion is
* done.
*/
final float x = ev.getX();
final int xDiff = (int) Math.abs(x - mLastMotionX);
boolean xMoved = xDiff > mTouchSlop;
if (xMoved) {
// Scroll if the user moved far enough along the X axis
mTouchState = TOUCH_STATE_HORIZONTAL_SCROLLING;
mLastMotionX = x;
}
final float y = ev.getY();
final int yDiff = (int) Math.abs(y - mLastMotionY);
boolean yMoved = yDiff > mTouchSlop;
if (yMoved) {
mTouchState = TOUCH_STATE_VERTICAL_SCROLLING;
}
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// Release the drag.
mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_DOWN:
/*
* No motion yet, but register the coordinates so we can check for intercept at the
* next MOVE event.
*/
mLastMotionY = ev.getY();
mLastMotionX = ev.getX();
break;
default:
break;
}
return intercept;
}
#Override
public boolean onTouchEvent(final MotionEvent ev) {
Log.e(TAG, "inside hp ontouchEvent");
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:
/*
* If being flinged and user touches, stop the fling. isFinished will be false if
* being flinged.
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
// Remember where the motion event started
mLastMotionX = x;
if (mScroller.isFinished()) {
mTouchState = TOUCH_STATE_REST;
} else {
mTouchState = TOUCH_STATE_HORIZONTAL_SCROLLING;
}
break;
case MotionEvent.ACTION_MOVE:
final int xDiff = (int) Math.abs(x - mLastMotionX);
boolean xMoved = xDiff > mTouchSlop;
if (xMoved) {
// Scroll if the user moved far enough along the X axis
mTouchState = TOUCH_STATE_HORIZONTAL_SCROLLING;
}
if (mTouchState == TOUCH_STATE_HORIZONTAL_SCROLLING) {
// Scroll to follow the motion event
final int deltaX = (int) (mLastMotionX - x);
mLastMotionX = x;
final int scrollX = getScrollX();
if (deltaX < 0) {
if (scrollX > 0) {
scrollBy(Math.max(-scrollX, deltaX), 0);
}
} else if (deltaX > 0) {
final int availableToScroll =
getChildAt(getChildCount() - 1).getRight() - scrollX - getWidth();
if (availableToScroll > 0) {
scrollBy(Math.min(availableToScroll, deltaX), 0);
}
}
}
break;
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_HORIZONTAL_SCROLLING) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(VELOCITY_UNIT_PIXELS_PER_SECOND,
mMaximumVelocity);
int velocityX = (int) velocityTracker.getXVelocity();
if (velocityX > mDensityAdjustedSnapVelocity && mCurrentScreen > 0) {
// Fling hard enough to move left
snapToScreen(mCurrentScreen - 1);
} else if (velocityX < -mDensityAdjustedSnapVelocity
&& mCurrentScreen < getChildCount() - 1) {
// Fling hard enough to move right
snapToScreen(mCurrentScreen + 1);
} else {
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_CANCEL:
mTouchState = TOUCH_STATE_REST;
break;
default:
break;
}
return true;
}
#Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
} else if (mNextScreen != INVALID_SCREEN) {
mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
// Notify observer about screen change
if (mOnScreenSwitchListener != null) {
mOnScreenSwitchListener.onScreenSwitched(mCurrentScreen);
}
mNextScreen = INVALID_SCREEN;
}
}
/**
* Returns the index of the currently displayed screen.
*
* #return The index of the currently displayed screen.
*/
public int getCurrentScreen() {
return mCurrentScreen;
}
/**
* Sets the current screen.
*
* #param currentScreen The new screen.
* #param animate True to smoothly scroll to the screen, false to snap instantly
*/
public void setCurrentScreen(final int currentScreen, final boolean animate) {
mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
if (animate) {
snapToScreen(currentScreen, ANIMATION_SCREEN_SET_DURATION_MILLIS);
} else {
scrollTo(mCurrentScreen * getWidth(), 0);
}
invalidate();
}
/**
* Sets the {#link OnScreenSwitchListener}.
*
* #param onScreenSwitchListener The listener for switch events.
*/
public void setOnScreenSwitchListener(final OnScreenSwitchListener onScreenSwitchListener) {
mOnScreenSwitchListener = onScreenSwitchListener;
}
/**
* Snaps to the screen we think the user wants (the current screen for very small movements; the
* next/prev screen for bigger movements).
*/
private void snapToDestination() {
final int screenWidth = getWidth();
int scrollX = getScrollX();
int whichScreen = mCurrentScreen;
int deltaX = scrollX - (screenWidth * mCurrentScreen);
// Check if they want to go to the prev. screen
if ((deltaX < 0) && mCurrentScreen != 0
&& ((screenWidth / FRACTION_OF_SCREEN_WIDTH_FOR_SWIPE) < -deltaX)) {
whichScreen--;
// Check if they want to go to the next screen
} else if ((deltaX > 0) && (mCurrentScreen + 1 != getChildCount())
&& ((screenWidth / FRACTION_OF_SCREEN_WIDTH_FOR_SWIPE) < deltaX)) {
whichScreen++;
}
snapToScreen(whichScreen);
}
/**
* Snap to a specific screen, animating automatically for a duration proportional to the
* distance left to scroll.
*
* #param whichScreen Screen to snap to
*/
private void snapToScreen(final int whichScreen) {
snapToScreen(whichScreen, -1);
}
/**
* Snaps to a specific screen, animating for a specific amount of time to get there.
*
* #param whichScreen Screen to snap to
* #param duration -1 to automatically time it based on scroll distance; a positive number to
* make the scroll take an exact duration.
*/
private void snapToScreen(final int whichScreen, final int duration) {
/*
* Modified by Yoni Samlan: Allow new snapping even during an ongoing scroll animation. This
* is intended to make HorizontalPager work as expected when used in conjunction with a
* RadioGroup used as "tabbed" controls. Also, make the animation take a percentage of our
* normal animation time, depending how far they've already scrolled.
*/
mNextScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
final int newX = mNextScreen * getWidth();
final int delta = newX - getScrollX();
if (duration < 0) {
// E.g. if they've scrolled 80% of the way, only animation for 20% of the duration
mScroller.startScroll(getScrollX(), 0, delta, 0, (int) (Math.abs(delta)
/ (float) getWidth() * ANIMATION_SCREEN_SET_DURATION_MILLIS));
} else {
mScroller.startScroll(getScrollX(), 0, delta, 0, duration);
}
Log.e(TAG, "about to call inval****************** on viewgroup");
invalidate();
}
/**
* Listener for the event that the HorizontalPager switches to a new view.
*/
public static interface OnScreenSwitchListener {
/**
* Notifies listeners about the new screen. Runs after the animation completed.
*
* #param screen The new screen index.
*/
void onScreenSwitched(int screen);
}
#Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
Log.e(TAG, "inside hp ondraw()");
}
output:
01-13 14:52:00.290: ERROR/*********horizontalpager(9206): inside hp constructor for xml inflation
01-13 14:52:00.295: ERROR/*********hpActivity(9206): image length = 13215
01-13 14:52:00.295: INFO/global(9206): Default buffer size used in BufferedInputStream constructor. It would be better to be explicit if an 8k buffer is required.
01-13 14:52:00.730: DEBUG/dalvikvm(9206): GC freed 353 objects / 40040 bytes in 58ms
01-13 14:52:00.740: ERROR/*********hpActivity(9206): b = not null
01-13 14:52:00.740: ERROR/*********hpActivity(9206): canvas created
01-13 14:52:00.740: ERROR/*********hpActivity(9206): view created
01-13 14:52:00.740: ERROR/*********hpActivity(9206): view added to viewgroup
01-13 14:52:00.740: ERROR/*********hpActivity(9206): finished handler
01-13 14:52:00.740: ERROR/*********hpActivity(9206): no of chidren = 1
01-13 14:52:00.775: ERROR/*********hpActivity(9206): about to inval viewgroup
01-13 14:52:00.775: ERROR/*********hpActivity(9206): finished inval viewgroup
Lose the Canvas, unless you intend to draw into the image you do not need it.
Your image loading code is horrific. Use BitmapFactory.decodeFile(..) instead.
The Bitmap object you get from BitmapFactory.decodeFile(..) should go inside an ImageView, not a View.
File file = new File(Environment.getExternalStorageDirectory(), "image.jpeg");
if (file.exists() && file.canRead()) {
Bitmap bitmap = BitmapFactory.decodeFile(file.toString());
if (bitmap != null) {
ImageView view = new ImageView(this);
view.setImageBitmap(bitmap);
viewgroup.addView(view);
}
}
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