Snap HorizontalScrollView - java

I got the code below from here.
This code snaps the items of HorizontalScrollView. I tried a lot to implement this customView inside my layout but I am not able to figure out to how to attach my layout to it.
This example does add the views programmatically and calls them Features.
In XML I have done "my package name.view" but I cannot figure out how to call setFeatureItems so that my Views can be attached to it.
The snapping feature can be applied on RecyclerView easily by using SnapHelper but I haven't found anything for HorizontalScrollView.
public class HomeFeatureLayout extends HorizontalScrollView {
private static final int SWIPE_MIN_DISTANCE = 5;
private static final int SWIPE_THRESHOLD_VELOCITY = 300;
private ArrayList mItems = null;
private GestureDetector mGestureDetector;
private int mActiveFeature = 0;
public HomeFeatureLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public HomeFeatureLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HomeFeatureLayout(Context context) {
super(context);
}
public void setFeatureItems(ArrayList items){
LinearLayout internalWrapper = new LinearLayout(getContext());
internalWrapper.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
internalWrapper.setOrientation(LinearLayout.HORIZONTAL);
addView(internalWrapper);
this.mItems = items;
for(int i = 0; i< items.size();i++){
LinearLayout featureLayout = (LinearLayout) View.inflate(this.getContext(),R.layout.homefeature,null);
//...
//Create the view for each screen in the scroll view
//...
internalWrapper.addView(featureLayout);
}
setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
//If the user swipes
if (mGestureDetector.onTouchEvent(event)) {
return true;
}
else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL ){
int scrollX = getScrollX();
int featureWidth = v.getMeasuredWidth();
mActiveFeature = ((scrollX + (featureWidth/2))/featureWidth);
int scrollTo = mActiveFeature*featureWidth;
smoothScrollTo(scrollTo, 0);
return true;
}
else{
return false;
}
}
});
mGestureDetector = new GestureDetector(new MyGestureDetector());
}
class MyGestureDetector extends SimpleOnGestureListener {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
//right to left
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
int featureWidth = getMeasuredWidth();
mActiveFeature = (mActiveFeature < (mItems.size() - 1))? mActiveFeature + 1:mItems.size() -1;
smoothScrollTo(mActiveFeature*featureWidth, 0);
return true;
}
//left to right
else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
int featureWidth = getMeasuredWidth();
mActiveFeature = (mActiveFeature > 0)? mActiveFeature - 1:0;
smoothScrollTo(mActiveFeature*featureWidth, 0);
return true;
}
} catch (Exception e) {
Log.e("Fling", "There was an error processing the Fling event:" + e.getMessage());
}
return false;
}
}
}

in your recycleview adapter you will have reference to HomeFeatureLayout there you can call
homeFeatureLayout.setFeatureItems(items);
EDIT
public void setFeatureItems(ArrayList items){
for(int i = 0; i< items.size();i++){
// here you need to provide layout of individual items in your horizontal scrollview
LinearLayout featureLayout = (LinearLayout) View.inflate(this.getContext(),R.layout.anyLayout,null);
//...
//display information here
//...
TextView title = (TextView)featureLayout.findviewById(R.id.title);
title.setText("view title "+item.get(i).getTitle());
ImageView image = (ImageView)featureLayout.findviewById(R.id.icon);
image.setResourceId(R.drawable.icon);
internalWrapper.addView(featureLayout);
}
}

Related

Why are swipes not being recognized in my code?

Im making an android game that only needs to recognize swipes. However, the onFling method is never called for some reason. I have no issue with modifying onScroll or other methods just so that onFling works. The GestureDetectGridView is just a basic class that calls the gridView super constructor. The Movement Controller is definitely correct as well. So the issue has to be in this class.
public class TwentyGestureDetectGridView extends GestureDetectGridView implements View.OnTouchListener, GestureDetector.OnGestureListener {
private static final int SWIPE_MIN_DISTANCE = 100;
private final GestureDetector gDetector;
private TwentyMovementController mController;
public TwentyGestureDetectGridView(Context context) {
super(context);
gDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener());
mController = new TwentyMovementController();
}
public TwentyGestureDetectGridView(Context context, AttributeSet attrs) {
super(context, attrs);
gDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener());
mController = new TwentyMovementController();
}
public TwentyGestureDetectGridView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
gDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener());
mController = new TwentyMovementController();
}
public void onSwipeUp(){}
public void onSwipeDown(){}
public void onSwipeLeft() {}
public void onSwipeRight() {}
public boolean onTouch(View v, MotionEvent event) {
return gDetector.onTouchEvent(event);
}
#Override
public boolean onDown(MotionEvent e) {
return true;
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
float distanceX = e2.getX() - e1.getX();
float distanceY = e2.getY() - e1.getY();
if (Math.abs(distanceX) > Math.abs(distanceY) && Math.abs(distanceX) > SWIPE_MIN_DISTANCE) {
if (distanceX > 0) {
onSwipeRight();
} else {
onSwipeLeft();
}
return true;
} else if (Math.abs(distanceY) > Math.abs(distanceX) && Math.abs(distanceY) > SWIPE_MIN_DISTANCE) {
if (distanceY > 0) {
onSwipeUp();
} else {
onSwipeDown();
}
return true;
}
return false;
}
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {return true;}
You are missing a call to View.setOnTouchListener
For the view you want to register swipes in (probably your base layout), call view.setOnTouchListener(your listener here)
Maybe like this:
#Override
public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstance){
View root = somehowCreateView();
root.setOnTouchListener(this);
return root;
}
public class TwentyGameActivity extends GameActivity implements Observer {
TwentyBoardManager twentyBoardManager;
private TwentyGestureDetectGridView gridView;
private static int columnWidth, columnHeight;
ArrayList<Button> tileButtons;
public void display() {
updateTileButtons();
gridView.setAdapter(new CustomAdapter(tileButtons, columnWidth, columnHeight));
}
private void createTileButtons(Context context){
TwentyBoard board = twentyBoardManager.getBoard();
tileButtons = new ArrayList<>();
for (int row = 0; row != board.getNumRows(); row++) {
for (int col = 0; col != board.getNumCols(); col++) {
Button tmp = new Button(context);
tmp.setBackgroundResource(board.getTile(row, col).getBackground());
this.tileButtons.add(tmp);
}
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
twentyBoardManager = (TwentyBoardManager) Savable.loadFromFile(TEMP_SAVE_FILENAME);
createTileButtons(this);
setContentView(R.layout.activity_twenty_game);
gridView = findViewById(R.id.gridTwenty);
gridView.setNumColumns(twentyBoardManager.getSize());
gridView.setBoardManager(twentyBoardManager);
twentyBoardManager.addObserver(this);
gridView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
gridView.getViewTreeObserver().removeOnGlobalLayoutListener(
this);
int displayWidth = gridView.getMeasuredWidth();
int displayHeight = gridView.getMeasuredHeight();
columnWidth = displayWidth / twentyBoardManager.twentyBoard.getNumCols();
columnHeight = displayHeight / twentyBoardManager.twentyBoard.getNumCols();
display();
}
});
addUndoButtonListener();
twentyBoardManager.twentyBoard.generateRandomTile();
}
private void addUndoButtonListener(){
Button undoButton = findViewById(R.id.undoTwentyButton);
undoButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
twentyBoardManager.undo();
}
});
}
#Override
public void update(Observable o, Object arg) {
display();
}
private void updateTileButtons() {
TwentyBoard board = twentyBoardManager.getBoard();
int nextPos = 0;
for (Button b : tileButtons) {
int row = nextPos / board.getNumCols();
int col = nextPos % board.getNumCols();
b.setBackgroundResource(board.getTile(row, col).getBackground());
nextPos++;
}
}
}

getView of element that you're swiping (onFling) for android?

I'm making a list with a linearLayout and adding TextViews depending on how many items I got in the database. Problem is that I want to be able to delete an item when I swipe left on the list. I was just wondering how to get the element (view) that you're swiping? Here's the code I got for adding a TextView.
for(int i = 0; i < nrOfTodos; i++) {
TextView v = new TextView(this);
String title = todos.get(i).getTitle();
System.out.println(title);
v.setText(title);
v.setHeight(listItemSize);
v.setAllCaps(true);
v.setTextColor(Color.parseColor("#FFD9A7"));
v.setTextSize(listItemSize/6);
v.setHorizontallyScrolling(false);
v.setMaxLines(1);
v.setEllipsize(TruncateAt.END);
v.setPadding(30, 50, 0, 0);
v.setId(i);
todoViews.add(v);
v.setOnTouchListener(new OnSwipeTouchListener(this) {
#Override
public void onSwipeLeft() {
/*
*
*
* TODO!!! NEXT PART OF MY PLAN TO WORLD DOMINATION!
*
*
*/
}
});
if(i%2 == 1) {
v.setBackgroundColor(Color.parseColor("#11FFFFFF"));
}
ll.addView(v, i);
}
and I also have this
public class OnSwipeTouchListener implements OnTouchListener {
private final GestureDetector gestureDetector;
public OnSwipeTouchListener(Context context) {
gestureDetector = new GestureDetector(context, new GestureListener());
}
public void onSwipeLeft() {
}
public void onSwipeRight() {
}
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
private final class GestureListener extends SimpleOnGestureListener {
private static final int SWIPE_DISTANCE_THRESHOLD = 50;
private static final int SWIPE_VELOCITY_THRESHOLD = 10;
#Override
public boolean onDown(MotionEvent e) {
//always return true since all gestures always begin with onDown and <br>
//if this returns false, the framework won't try to pick up onFling for example.
return true;
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
float distanceX = e2.getX() - e1.getX();
float distanceY = e2.getY() - e1.getY();
if (Math.abs(distanceX) > Math.abs(distanceY) && Math.abs(distanceX) > SWIPE_DISTANCE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (distanceX > 0)
onSwipeRight();
else
onSwipeLeft();
return true;
}
return false;
}
}
}
but I can't figure out how to actually get the view, get the titletext and remove that from the database (I can remove from the database, but I can't get what item was swiped ^^).
Any ideas would be greatly appreciated.
You may refer to this sample application Swipe_To_Delete
You may use ListView to list your data items which are got from the db.
Then modify the getSwipeItem() method in the MainActivity of the example, as follows.
#Override
public void getSwipeItem(boolean isRight, int position) {
[List_Of_Items].remove(position);
[Your_Adapter].notifyDataSetChanged();
}

Listview with pull to refresh Scroll down to last item when adding header-view

I have created one pull to refresh listview now when I scroll or pull little bit then I have to add header view.
here my custom listview class
package com.app.refreshableList;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.app.xxxxxx.R;
import com.google.android.gms.internal.el;
public class RefreshableListView extends ListView {
Boolean isScrool = false;
private View mHeaderContainer = null;
private View mHeaderView = null;
private ImageView mArrow = null;
private ProgressBar mProgress = null;
private TextView mText = null;
private float mY = 0;
private float mHistoricalY = 0;
private int mHistoricalTop = 0;
private int mInitialHeight = 0;
private boolean mFlag = false;
private boolean mArrowUp = false;
private boolean mIsRefreshing = false;
private int mHeaderHeight = 0;
private OnRefreshListener mListener = null;
private static final int REFRESH = 0;
private static final int NORMAL = 1;
private static final int HEADER_HEIGHT_DP = 62;
private static final String TAG = RefreshableListView.class.getSimpleName();
private ListViewObserver mObserver;
private View mTrackedChild;
private int mTrackedChildPrevPosition;
private int mTrackedChildPrevTop;
OnTouchListener touch;
View vHeader;
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mTrackedChild == null) {
if (getChildCount() > 0) {
mTrackedChild = getChildInTheMiddle();
mTrackedChildPrevTop = mTrackedChild.getTop();
mTrackedChildPrevPosition = getPositionForView(mTrackedChild);
}
} else {
boolean childIsSafeToTrack = mTrackedChild.getParent() == this
&& getPositionForView(mTrackedChild) == mTrackedChildPrevPosition;
if (childIsSafeToTrack) {
int top = mTrackedChild.getTop();
if (mObserver != null) {
float deltaY = top - mTrackedChildPrevTop;
mObserver.onScroll(deltaY);
}
mTrackedChildPrevTop = top;
} else {
mTrackedChild = null;
}
}
}
private View getChildInTheMiddle() {
return getChildAt(getChildCount() / 2);
}
public void setObserver(ListViewObserver observer) {
mObserver = observer;
}
public RefreshableListView(final Context context) {
super(context);
initialize();
}
public RefreshableListView(final Context context, final AttributeSet attrs) {
super(context, attrs);
initialize();
}
public RefreshableListView(final Context context, final AttributeSet attrs,
final int defStyle) {
super(context, attrs, defStyle);
initialize();
}
public void setOnRefreshListener(final OnRefreshListener l) {
mListener = l;
}
#Override
public void setOnTouchListener(OnTouchListener l) {
// TODO Auto-generated method stub
super.setOnTouchListener(l);
}
public void completeRefreshing() {
mProgress.setVisibility(View.INVISIBLE);
mArrow.setVisibility(View.VISIBLE);
mHandler.sendMessage(mHandler.obtainMessage(NORMAL, mHeaderHeight, 0));
mIsRefreshing = false;
invalidateViews();
}
#Override
public boolean onInterceptTouchEvent(final MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mHandler.removeMessages(REFRESH);
mHandler.removeMessages(NORMAL);
mY = mHistoricalY = ev.getY();
if (mHeaderContainer.getLayoutParams() != null) {
mInitialHeight = mHeaderContainer.getLayoutParams().height;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
#Override
public boolean onTouchEvent(final MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
mHistoricalTop = getChildAt(0).getTop();
break;
case MotionEvent.ACTION_UP:
if (!mIsRefreshing) {
if (mArrowUp) {
startRefreshing();
mHandler.sendMessage(mHandler.obtainMessage(REFRESH,
(int) (ev.getY() - mY) / 2 + mInitialHeight, 0));
} else {
if (getChildAt(0).getTop() == 0) {
mHandler.sendMessage(mHandler.obtainMessage(NORMAL,
(int) (ev.getY() - mY) / 2 + mInitialHeight, 0));
}
}
} else {
mHandler.sendMessage(mHandler.obtainMessage(REFRESH,
(int) (ev.getY() - mY) / 2 + mInitialHeight, 0));
}
mFlag = false;
break;
}
return super.onTouchEvent(ev);
}
#Override
public boolean dispatchTouchEvent(final MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_MOVE
&& getFirstVisiblePosition() == 0) {
float direction = ev.getY() - mHistoricalY;
int height = (int) (ev.getY() - mY) / 2 + mInitialHeight;
if (height < 0) {
height = 0;
}
float deltaY = Math.abs(mY - ev.getY());
ViewConfiguration config = ViewConfiguration.get(getContext());
if (deltaY > config.getScaledTouchSlop()) {
// Scrolling downward
if (direction > 0) {
// Refresh bar is extended if top pixel of the first item is
// visible
if (getChildAt(0) != null) {
if (getChildAt(0).getTop() == 0) {
if (mHistoricalTop < 0) {
// mY = ev.getY(); // TODO works without
// this?mHistoricalTop = 0;
}
// if (isScrool == true) {
//
// } else {
// isScrool = true;
// addHeaderView(vHeader);
//
// // Animation anim = AnimationUtils.loadAnimation(
// // getContext(), R.anim.bounce_animation);
// // startAnimation(anim);
//
// smoothScrollToPosition(getChildAt(0).getTop());
// // bottom_layout.setVisibility(View.VISIBLE);
// }
// Extends refresh bar
/*****
* commented by me on 10-09-2014
*/
setHeaderHeight(height);
// Stop list scroll to prevent the list from
// overscrolling
ev.setAction(MotionEvent.ACTION_CANCEL);
mFlag = false;
}
}
} else if (direction < 0) {
// Scrolling upward
// Refresh bar is shortened if top pixel of the first item
// is
// visible
if (getChildAt(0) != null) {
if (getChildAt(0).getTop() == 0) {
setHeaderHeight(height);
// If scroll reaches top of the list, list scroll is
// enabled
if (getChildAt(1) != null
&& getChildAt(1).getTop() <= 1 && !mFlag) {
ev.setAction(MotionEvent.ACTION_DOWN);
mFlag = true;
}
}
}
}
}
mHistoricalY = ev.getY();
}
try {
return super.dispatchTouchEvent(ev);
} catch (Exception e) {
return false;
}
}
#Override
public boolean performItemClick(final View view, final int position,
final long id) {
if (position == 0) {
// This is the refresh header element
return true;
} else {
return super.performItemClick(view, position - 1, id);
}
}
private void initialize() {
LayoutInflater inflator = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
vHeader = inflator.inflate(R.layout.search_header, null);
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mHeaderContainer = inflater.inflate(R.layout.refreshable_list_header,
null);
mHeaderView = mHeaderContainer
.findViewById(R.id.refreshable_list_header);
mArrow = (ImageView) mHeaderContainer
.findViewById(R.id.refreshable_list_arrow);
mProgress = (ProgressBar) mHeaderContainer
.findViewById(R.id.refreshable_list_progress);
mText = (TextView) mHeaderContainer
.findViewById(R.id.refreshable_list_text);
addHeaderView(mHeaderContainer);
mHeaderHeight = (int) (HEADER_HEIGHT_DP * getContext().getResources()
.getDisplayMetrics().density);
setHeaderHeight(0);
}
private void setHeaderHeight(final int height) {
if (height <= 1) {
mHeaderView.setVisibility(View.GONE);
} else {
mHeaderView.setVisibility(View.VISIBLE);
}
// Extends refresh bar
LayoutParams lp = (LayoutParams) mHeaderContainer.getLayoutParams();
if (lp == null) {
lp = new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT);
}
lp.height = height;
mHeaderContainer.setLayoutParams(lp);
// Refresh bar shows up from bottom to top
LinearLayout.LayoutParams headerLp = (LinearLayout.LayoutParams) mHeaderView
.getLayoutParams();
if (headerLp == null) {
headerLp = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT);
}
headerLp.topMargin = -mHeaderHeight + height;
mHeaderView.setLayoutParams(headerLp);
if (!mIsRefreshing) {
// If scroll reaches the trigger line, start refreshing
if (height > mHeaderHeight && !mArrowUp) {
mArrow.startAnimation(AnimationUtils.loadAnimation(
getContext(), R.anim.rotate));
mText.setText("Release to update");
rotateArrow();
mArrowUp = true;
} else if (height < mHeaderHeight && mArrowUp) {
mArrow.startAnimation(AnimationUtils.loadAnimation(
getContext(), R.anim.rotate));
mText.setText("Pull down to update");
rotateArrow();
mArrowUp = false;
}else {
if (isScrool == true) {
} else {
isScrool = true;
addHeaderView(vHeader);
// Animation anim = AnimationUtils.loadAnimation(
// getContext(), R.anim.bounce_animation);
// startAnimation(anim);
smoothScrollToPosition(getChildAt(0).getTop());
// bottom_layout.setVisibility(View.VISIBLE);
}
}
}
}
private void rotateArrow() {
Drawable drawable = mArrow.getDrawable();
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.save();
canvas.rotate(180.0f, canvas.getWidth() / 2.0f,
canvas.getHeight() / 2.0f);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight());
drawable.draw(canvas);
canvas.restore();
mArrow.setImageBitmap(bitmap);
}
private void startRefreshing() {
mArrow.setVisibility(View.INVISIBLE);
mProgress.setVisibility(View.VISIBLE);
mText.setText("Loading...");
mIsRefreshing = true;
if (mListener != null) {
mListener.onRefresh(this);
}
}
private final Handler mHandler = new Handler() {
#Override
public void handleMessage(final Message msg) {
super.handleMessage(msg);
int limit = 0;
switch (msg.what) {
case REFRESH:
limit = mHeaderHeight;
break;
case NORMAL:
limit = 0;
break;
}
// Elastic scrolling
if (msg.arg1 >= limit) {
setHeaderHeight(msg.arg1);
int displacement = (msg.arg1 - limit) / 10;
if (displacement == 0) {
mHandler.sendMessage(mHandler.obtainMessage(msg.what,
msg.arg1 - 1, 0));
} else {
mHandler.sendMessage(mHandler.obtainMessage(msg.what,
msg.arg1 - displacement, 0));
}
}
}
};
public interface OnRefreshListener {
public void onRefresh(RefreshableListView listView);
}
public static interface ListViewObserver {
public void onScroll(float deltaY);
}
}
here in method dispatchTouchEvent(final MotionEvent ev) i have make one condition
if (isScrool == true) {
} else {
isScrool = true;
addHeaderView(vHeader);
// Animation anim =
// AnimationUtils.loadAnimation(
// getContext(), R.anim.bounce_animation);
// startAnimation(anim);
smoothScrollToPosition(getChildAt(0).getTop());
// bottom_layout.setVisibility(View.VISIBLE);
}
here addHeaderView(vHeader);
adds header but my listview scroll down to last item can anyone could tell me what's happening here?
Try to call listView.setSelectionAfterHeaderView();. This will scroll ListView to top.

Animating list items that are partially hidden or at the end of the currently visible views in a ListView

I'm currently implementing my own custom listview. I idea is that a longclick on a list item will scale the surrounding visible views to a smaller value scale and leave the the current view normal, whilst expanding a view below the current selected view. Once the user touches anywhere around the selected view, it will scale everything back to normal.
Unfortunately, there's an issue whereby the last view maybe partially hidden during the scale animation and so doesn't scale back to normal. So when you try to gain focus to the rest of the Listview by touching outside the selected list item, it remains a smaller scale.
I've tried a few approaches, from using ViewTreeObserver to storing the positions of the list items to make sure that they get scaled, but nothing has worked. Below is my custom listview:
public class ZoomListView extends ListView implements AdapterView.OnItemLongClickListener {
private static final String TAG = ZoomListView.class.getName();
private int _xPos;
private int _yPos;
private int _pointerId;
private Rect _viewBounds;
private boolean _isZoomed;
private OnItemClickListener _listner;
private OnItemFocused _onItemFocusedLis;
private int _expandingViewHeight = 0;
private int _previousFocusedViewHeight;
public interface OnItemFocused {
/**
* This interface can be used to be notified when a particular item should be disabled, or is currently not focused
* #param position
*/
public void onItemOutOfFocus(int position, boolean status_);
public View onItemFocused(View focusedView_, int listViewPosition_, long uniqueId_);
}
public ZoomListView(Context context_) {
super(context_);
init(context_);
}
public ZoomListView(Context context_, AttributeSet attrs) {
super(context_, attrs);
init(context_);
}
public ZoomListView(Context context_, AttributeSet attrs, int defStyle) {
super(context_, attrs, defStyle);
init(context_);
}
#Override
public void setOnItemClickListener(OnItemClickListener listener) {
if(!(listener == null)){
_listner = listener;
}
super.setOnItemClickListener(listener);
}
private void init(Context context_){
setOnItemLongClickListener(this);
}
public void setOnItemDisableListener(OnItemFocused listener_){
_onItemFocusedLis = listener_;
}
private void scaleChildViews(long rowId_, int itemPos_, float scale, boolean shouldEnable){
if (_isZoomed) {
getParent().requestDisallowInterceptTouchEvent(true);
}
int firstVisiblePosition = getFirstVisiblePosition();
int pos = pointToPosition(_xPos, _yPos);
int positionOrg = pos - firstVisiblePosition;
scaleAllVisibleViews(positionOrg, scale, shouldEnable);
}
private void scaleAllVisibleViews(final int clickedItemPosition_, final float scale_, final boolean shouldEnable_) {
Animation scaleAnimation;
if(_isZoomed){
scaleAnimation = getZoomAnimation(1f, 0.8f, 1f, 0.8f);
}else{
scaleAnimation = getZoomAnimation(0.8f, 1f, 0.8f, 1f);
}
int firstVisiblePosition = getFirstVisiblePosition();
int count = getChildCount();
for (int i = 0; i < count; i++) {
int pos = i;
if (_isZoomed) {
if (getAdapter().getItemId(clickedItemPosition_) != getAdapter().getItemId(pos)) {
scaleView(pos, scale_, shouldEnable_, scaleAnimation);
}else{
displayExpandingView(pos, clickedItemPosition_);
}
} else {
View view = getChildAt(pos);
View viewToShow = _onItemFocusedLis.onItemFocused(view, pos, getAdapter().getItemId(clickedItemPosition_));
if(viewToShow != null){
viewToShow.setVisibility(GONE);
}
scaleView(pos, scale_, shouldEnable_, scaleAnimation);
}
}
}
private void displayExpandingView(int position_, int clickedItemPosition_){
View view = getChildAt(position_);
if(view != null){
Log.v(TAG, "view is valid");
View viewToShow = _onItemFocusedLis.onItemFocused(view, position_, getAdapter().getItemId(clickedItemPosition_));
viewToShow.setVisibility(VISIBLE);
Animation flip = new CyclicFlipAnimation(50f);
flip.setDuration(500);
viewToShow.startAnimation(flip);
if(_expandingViewHeight <= 0){
viewToShow.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
_expandingViewHeight = viewToShow.getMeasuredHeight();
Log.v(TAG, "expanding view hieght is + " + _expandingViewHeight);
}
}
}
private Animation getZoomAnimation(float fromX_, float toX_, float fromY_, float toY_){
Animation scaleAnimation = new ScaleAnimation(
fromX_, toX_,
fromY_, toY_,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setFillAfter(true);
scaleAnimation.setDuration(500);
return scaleAnimation;
}
private void scaleView(int position_, float scale_, boolean shouldEnable_, Animation animation_){
View view = getChildAt(position_);
if (view != null) {
view.startAnimation(animation_);
if (_onItemFocusedLis != null) {
_onItemFocusedLis.onItemOutOfFocus(position_, shouldEnable_);
}
}
}
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
_isZoomed = true;
scaleChildViews(l, i, 0.8f, false);
return true;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
_xPos = (int) event.getX();
_yPos = (int) event.getY();
_pointerId = event.getPointerId(0);
if (_isZoomed) {
if (!_viewBounds.contains(_xPos, _yPos)) {
_isZoomed = false;
scaleChildViews(1, 1, 1f, true);
}
return false;
}
int position = pointToPosition(_xPos, _yPos);
int childNum = (position != INVALID_POSITION) ? position - getFirstVisiblePosition() : -1;
View itemView = (childNum >= 0) ? getChildAt(childNum) : null;
if (itemView != null) {
_viewBounds = getChildViewRect(this, itemView);
}
break;
}
return super.onTouchEvent(event);
}
private Rect getChildViewRect(View parentView, View childView) {
final Rect childRect = new Rect(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom() + _expandingViewHeight);
if (parentView == childView) {
return childRect;
}
ViewGroup parent = (ViewGroup) childView.getParent();
while (parent != parentView) {
childRect.offset(parent.getLeft(), parent.getTop());
childView = parent;
parent = (ViewGroup) childView.getParent();
}
return childRect;
}
}
Any idea's on what maybe wrong, thanks.
To fix this issue I ended up adding a ACTION_MOVE case statement in my onTouch method. So as long as the list items were focused again, when the list performed a move i.e. scrolled the list item, the remaining list items are restored to their original size. To achieve this you need to maintain a map/collection of id's that correspond to your list items and iterate over them once the user scrolls.
Heres the full somwhat functiong implementation:
package com.sun.tweetfiltrr.zoomlistview;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
import com.sun.tweetfiltrr.animation.CyclicFlipAnimation;
import java.util.HashMap;
import java.util.Map;
/**
*/
public class ZoomListView extends ListView implements AdapterView.OnItemLongClickListener {
private static final String TAG = ZoomListView.class.getName();
private int _xPos;
private int _yPos;
private int _pointerId;
private Rect _viewBounds;
private boolean _isZoomed;
private OnItemClickListener _listner;
private OnItemFocused _onItemFocusedLis;
private int _expandingViewHeight = 0;
private int _previousFocusedViewHeight;
private OnScrollListener _onScrollListener;
private boolean _shouldPerformScrollAnimation;
final private Map<Long, PropertyHolder> _itemIDToProperty = new HashMap<Long, PropertyHolder>();
private long _currentFocusedId;
public interface OnItemFocused {
/**
* This interface can be used to be notified when a particular item should be disabled, or is currently not focused
* #param position
*/
public void onItemScaleOut(int position, View view, boolean status_);
public void onItemRestore(int position, View view, boolean status_);
public View onItemFocused(View focusedView_, int listViewPosition_, long uniqueId_);
}
public ZoomListView(Context context_) {
super(context_);
init(context_);
}
public ZoomListView(Context context_, AttributeSet attrs) {
super(context_, attrs);
init(context_);
}
public ZoomListView(Context context_, AttributeSet attrs, int defStyle) {
super(context_, attrs, defStyle);
init(context_);
}
private void init(Context context_){
setOnItemLongClickListener(this);
}
public void setOnItemDisableListener(OnItemFocused listener_){
_onItemFocusedLis = listener_;
}
private void scaleChildViews(long rowId_, int itemPos_, float scale, boolean shouldEnable){
if (_isZoomed) {
getParent().requestDisallowInterceptTouchEvent(true);
}
scaleAllVisibleViews(shouldEnable);
}
private void scaleAllVisibleViews(final boolean shouldEnable_) {
final ListAdapter adapter = getAdapter();
Animation scaleAnimation;
if(_isZoomed){
scaleAnimation = getZoomAnimation(1f, 0.6f, 1f, 0.6f);
}else{
scaleAnimation = getZoomAnimation(0.6f, 1f, 0.6f, 1f);
}
int count = getChildCount();
for (int i = 0; i < count; i++) {
applyAnimation(i, adapter, shouldEnable_, scaleAnimation);
}
}
private void applyAnimation(int position_, ListAdapter adapter_, boolean shouldEnable_, Animation animation_){
if (_isZoomed) {
if (_currentFocusedId != adapter_.getItemId(position_)) {
scaleView(position_, shouldEnable_, animation_);
}else{
displayExpandingView(position_, adapter_);
}
}else{
if(_currentFocusedId != getAdapter().getItemId(position_)){
scaleView(position_, shouldEnable_, animation_);
}else{
View view = getChildAt(position_);
View viewToShow = _onItemFocusedLis.onItemFocused(view, position_, getAdapter().getItemId(position_));
if(viewToShow != null){
viewToShow.setVisibility(GONE);
}
}
_itemIDToProperty.remove(getAdapter().getItemId(position_));
}
}
private void displayExpandingView(int position_, ListAdapter adapter_){
View view = getChildAt(position_);
if(view != null){
View viewToShow = _onItemFocusedLis.onItemFocused(view, position_, adapter_.getItemId(position_));
viewToShow.setVisibility(VISIBLE);
Animation flip = new CyclicFlipAnimation(60f);
flip.setDuration(1000);
viewToShow.startAnimation(flip);
if(_expandingViewHeight <= 0){
viewToShow.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
_expandingViewHeight = viewToShow.getMeasuredHeight();
}
}
}
private Animation getZoomAnimation(float fromX_, float toX_, float fromY_, float toY_){
Animation scaleAnimation = new ScaleAnimation(
fromX_, toX_,
fromY_, toY_,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.ABSOLUTE, 0.7f);
scaleAnimation.setFillAfter(true);
scaleAnimation.setDuration(500);
return scaleAnimation;
}
private void scaleView(int position_, boolean shouldEnable_, Animation animationScale_ ){
View view = getChildAt(position_);
ListAdapter adapter = getAdapter();
long id = adapter.getItemId(position_);
view.startAnimation(animationScale_);
PropertyHolder holder = _itemIDToProperty.get(id);
if (holder == null) {
holder = new PropertyHolder((int) view.getTop(), (int) (view.getBottom()));
_itemIDToProperty.put(id, holder);
}
int h = view.getHeight();
if (_isZoomed) {
view.animate().translationYBy((h * 0.5f)).setDuration(500).start();
if (_onItemFocusedLis != null) {
_onItemFocusedLis.onItemScaleOut(position_, view, shouldEnable_);
}
} else {
view.animate().translationYBy(-(h * 0.5f )).setDuration(500).start();
if (_onItemFocusedLis != null) {
_onItemFocusedLis.onItemRestore(position_, view, shouldEnable_);
}
}
}
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
_isZoomed = true;
int firstVisiblePosition = getFirstVisiblePosition();
int pos = pointToPosition(_xPos, _yPos);
int positionOrg = pos - firstVisiblePosition;
_currentFocusedId = getAdapter().getItemId(positionOrg);
scaleChildViews(l, i, 0.8f, false);
return true;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
_xPos = (int) event.getX();
_yPos = (int) event.getY();
_pointerId = event.getPointerId(0);
if (_isZoomed) {
if (!_viewBounds.contains(_xPos, _yPos)) {
_isZoomed = false;
scaleChildViews(1, 1, 1f, true);
}
return false;
}
int position = pointToPosition(_xPos, _yPos);
int childNum = (position != INVALID_POSITION) ? position - getFirstVisiblePosition() : -1;
View itemView = (childNum >= 0) ? getChildAt(childNum) : null;
if (itemView != null) {
_viewBounds = getChildViewRect(this, itemView);
}
break;
case MotionEvent.ACTION_MOVE:
if (!_isZoomed) {
animateRemaining();
}
break;
}
return super.onTouchEvent(event);
}
private void animateRemaining(){
if(!_itemIDToProperty.isEmpty()){
for(int i = 0; i < getChildCount(); i++){
long id = getAdapter().getItemId(i);
PropertyHolder n = _itemIDToProperty.get(id);
if(n != null){
if(_currentFocusedId != getAdapter().getItemId(i)){
scaleView(i, true, getZoomAnimation(0.6f, 1, 0.6f, 1f));
}else{
Log.v(TAG, "not translating for " + getAdapter().getItemId(i)+ " for id " + _currentFocusedId);
}
_itemIDToProperty.remove(id);
}
}
}
}
private Rect getChildViewRect(View parentView, View childView) {
final Rect childRect = new Rect(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom() + _expandingViewHeight);
if (parentView == childView) {
return childRect;
}
ViewGroup parent = (ViewGroup) childView.getParent();
while (parent != parentView) {
childRect.offset(parent.getLeft(), parent.getTop());
childView = parent;
parent = (ViewGroup) childView.getParent();
}
return childRect;
}
private class PropertyHolder{
int _top;
int _bot;
private PropertyHolder(int top_, int bot_){
_top = top_;
_bot = bot_;
}
}
}
It's far from perfect but its a starting point.

Android actionbar: Custom fade animation on tab icon and title

I'm trying to make a custom animation that works pretty much the same way the tumblr android app works using the standard actionbar. (Not fading of the content of the tabs) I'm pretty close to getting it working but I have troubles fixing the last part. Maybe someone here can help me in the right direction to get this working.
EDIT: Image to show what I mean: IMAGE
What I have.
In onCreate I add some custom views for the tabs:
actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayUseLogoEnabled(false);
actionBar.setDisplayShowHomeEnabled(false);
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
final CustomPageTransformer cpt = new CustomPageTransformer();
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
cpt.currentItem = position;
}
});
mViewPager.setPageTransformer(true, cpt);
CustomTabView tabNews = new CustomTabView(this, mSectionsPagerAdapter.getPageTitle(0).toString(), mSectionsPagerAdapter.getIcon(0));
CustomTabView tabEvents = new CustomTabView(this, mSectionsPagerAdapter.getPageTitle(1).toString(), mSectionsPagerAdapter.getIcon(1));
CustomTabView tabContacts = new CustomTabView(this, mSectionsPagerAdapter.getPageTitle(2).toString(), mSectionsPagerAdapter.getIcon(2));
CustomTabView tabClub = new CustomTabView(this, mSectionsPagerAdapter.getPageTitle(3).toString(), mSectionsPagerAdapter.getIcon(3));
actionBar.addTab(actionBar.newTab().setCustomView(tabNews).setTabListener(this).setTag(0));
actionBar.addTab(actionBar.newTab().setCustomView(tabEvents).setTabListener(this).setTag(1));
actionBar.addTab(actionBar.newTab().setCustomView(tabContacts).setTabListener(this).setTag(2));
actionBar.addTab(actionBar.newTab().setCustomView(tabClub).setTabListener(this).setTag(3));
The CustomTabView looks like this, where I've added two custom view so I can easily set the alpha of the views:
public class CustomTabView extends RelativeLayout {
AlphaTextView text1;
AlphaImageView icon1;
int alpha;
public CustomTabView(Context context, String text, Drawable icon) {
super(context);
init(context, text, icon);
}
public CustomTabView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, null, null);
}
public CustomTabView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, null, null);
}
private void init(Context context, String text, Drawable icon) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.tab_view, this, true);
text1 = (AlphaTextView) findViewById(R.id.title);
text1.setTypeface(Font.rMedium(context));
text1.setText(text);
icon1 = (AlphaImageView) findViewById(R.id.icon);
icon1.setImageDrawable(icon);
setTheAlpha(128);
}
public void setTheAlpha(int aleph) {
this.alpha = aleph;
if (alpha < 128) alpha = 128;
if (alpha > 255) alpha = 255;
text1.onSetAlpha(alpha);
icon1.onSetAlpha(alpha);
}
public int getTheAlpha() {
return alpha;
}
}
Then there is the problem, the CustomPageTransformer:
public class CustomPageTransformer implements ViewPager.PageTransformer {
public int currentItem = 0;
View currentView = null;
public CustomTabView tabNews = null;
public CustomTabView tabEvents = null;
public CustomTabView tabContacts = null;
public CustomTabView tabClub = null;
#Override
public void transformPage(View view, float position) {
if (position != 0.0 && position != 1.0 && position != 2.0 && position != -1.0) {
if (currentView == null) {
currentView = view;
Log.e("First number", String.valueOf(position));
}
} else if (position == 0.0 || position == 1.0 || position == 2.0 || position == -1.0 || position == -2.0) currentView = null;
if (view == currentView) {
int alphaCurrent = (int) (255 - (128*Math.abs(position)));
if (alphaCurrent > 255) alphaCurrent = 255;
else if (alphaCurrent < 128) alphaCurrent = 128;
int alphaNext = (int) (128 + (128*Math.abs(position)));
if (alphaNext > 255) alphaNext = 255;
else if (alphaNext < 128) alphaNext = 128;
if (tabNews != null && tabEvents != null && tabContacts != null && tabClub != null) {
switch(currentItem) {
case 0:
if (position <= -1) {
tabNews.setTheAlpha(128);
tabEvents.setTheAlpha(255);
} else if (position <= 0) {
tabNews.setTheAlpha(alphaCurrent);
tabEvents.setTheAlpha(alphaNext);
}
break;
case 1:
if (position <= -1) {
tabEvents.setTheAlpha(128);
tabContacts.setTheAlpha(255);
} else if (position <= 0) {
tabEvents.setTheAlpha(alphaCurrent);
tabContacts.setTheAlpha(alphaNext);
} else if (position <= 1) {
tabEvents.setTheAlpha(alphaCurrent);
tabNews.setTheAlpha(alphaNext);
} else if (position > 1) {
tabEvents.setTheAlpha(128);
tabNews.setTheAlpha(255);
}
break;
case 2:
if (position <= -1) {
tabContacts.setTheAlpha(128);
tabClub.setTheAlpha(255);
} else if (position <= 0) {
tabContacts.setTheAlpha(alphaCurrent);
tabClub.setTheAlpha(alphaNext);
} else if (position <= 1) {
tabContacts.setTheAlpha(alphaCurrent);
tabEvents.setTheAlpha(alphaNext);
} else if (position > 1) {
tabEvents.setTheAlpha(255);
tabContacts.setTheAlpha(128);
}
break;
case 3:
if (position <= 1) {
tabClub.setTheAlpha(alphaCurrent);
tabContacts.setTheAlpha(alphaNext);
} else if (position > 1) {
tabClub.setTheAlpha(128);
tabContacts.setTheAlpha(255);
}
break;
}
}
}
}
}
The transformPage method gets (View & Position). The view is always the fragment you are going to and from in the viewpager, so every second time I have a positive value in position and then the next a negative value depending on which direction you are sliding.
First I thought I could use the view to determine which the current tab is and what direction I'm going in by taking whatever view comes first, but that seems to be random.
And since the position value jumps from negative to positive it generates a bit of a random fading result in the tabs.
Any ideas of how I can get this to work smoothly?
I found a better way of achieving this without using the page transformer. By using setOnPageChangeListener.
I've made an example app here: https://github.com/andreborud/FadingTabs
The code is pretty much just this:
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int alphaCurrent = (int) (255 - (128*Math.abs(positionOffset)));
int alphaNext = (int) (128 + (128*Math.abs(positionOffset)));
if (positionOffset != 0) {
switch(position) {
case 0:
tab0.setTheAlpha(alphaCurrent);
tab1.setTheAlpha(alphaNext);
break;
case 1:
tab1.setTheAlpha(alphaCurrent);
tab2.setTheAlpha(alphaNext);
break;
case 2:
tab2.setTheAlpha(alphaCurrent);
tab3.setTheAlpha(alphaNext);
break;
}
}
}
});

Categories

Resources