I have a Listview with fullscreen pictures. (Like a vertical gallery). My problem is that I want to override the listviews onTouchEvent so that it only changes one picture at a time and so that after scrolling (When no fling occurs), the selected picure centers itself.
My problem is that when i call getChildAt(getFirstVisiblePosition()), it returns null. I allready checked and getFirstVisiblePosition() is returning a view that is vissible at the moment.
I'm using smoothScrollToPosition to center the pictures.
My code is verry long, but the important part is here: (when recieving a MotionEvent.ACTION_UP).
case MotionEvent.ACTION_UP:
{
try
{
//Calculetes the velocity if the movement
int initialVelocity = (int)ModifiedVelocityTracker.getYVelocity();*/
if (mVelocityTracker == null)
{
mVelocityTracker = VelocityTracker.obtain();
}
final VelocityTracker ModifiedVelocityTracker = mVelocityTracker;
ModifiedVelocityTracker.computeCurrentVelocity(1000);
int initialVelocity = (int)ModifiedVelocityTracker.getYVelocity();
//Detects if the motion is a fling
if ((Math.abs(initialVelocity) >
ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) &&
(getChildCount() > 0)) // TODO FLing
{
super.onTouchEvent(getEventWithACTION_MOVE(ev));
//Send the events to cancel the superclass fling
//MotionEvent me[] = mVelocityTracker.getTrickEvents();
MotionEvent me[] = getTrickEvents(ev);
for(int i=0;i<me.length;i++)
super.onTouchEvent(me[i]);
int firstVisiblePosition = getFirstVisiblePosition();
// Pint y=0 is located on the bottom, so a negative speed means
//the finger moved up and the picture to come is the one under
if(initialVelocity < 0) //EventUp-GoDown
{
if(firstVisiblePosition != getCount() - 1) //Can Move
{
//smoothScrollToPosition(firstVisiblePosition + 1);
View currentTopView = getChildAt(firstVisiblePosition);
int botom = currentTopView.getBottom();
botom = botom - firstVisiblePosition * getHeight();
smoothScrollBy(-botom, 500);
} else
{
smoothScrollToPosition(firstVisiblePosition);
}
} else //EventDown-GoUp
{
smoothScrollToPosition(firstVisiblePosition);
}
}
else // TODO Stay in the picture that has a bigger area shown
{
onTouchEvent = super.onTouchEvent(ev);
//Center The View after the parent stops moving it
/*
* FIREST VIEW |****| / SECOND VIEW | |
*
* CASE 1 Bottom over center - Set second view the main one
* |****|
* |****| Bottom of Top View at First visible Position
* | |
* |----| center line
* | |
* | |
* | |
*
* CASE 2 Bottom under center - Set first view the main one
* |****|
* |****|
* |****|
* |----| center line
* |****|
* |****| Bottom of Top View at First visible Position
* | |
* */
int firstVisiblePosition = getFirstVisiblePosition();
View currentTopView = getChildAt(firstVisiblePosition);
int botom;
if(currentTopView != null)
{
botom = currentTopView.getBottom();
}else{
currentTopView = getSelectedView();
if(firstVisiblePosition == getPositionForView(currentTopView))
{
botom = currentTopView.getBottom();
}
else
{
botom = currentTopView.getTop();
}
}
int center = getHeight()/2;
botom = botom - firstVisiblePosition * getHeight();
if(botom < center) //Case 1 - Scroll Down
{
//Checks if the top view is the last one.
//Shouldn't happen, but just in case.
if(firstVisiblePosition != getCount() - 1) //Can Move
{
smoothScrollToPosition(firstVisiblePosition + 1);
} else
{
smoothScrollToPosition(firstVisiblePosition);
}
}
else //Case 2
{
smoothScrollToPosition(firstVisiblePosition);
}
}
onTouchEvent = true;
} catch(NullPointerException e)
{
e.printStackTrace();
}
}
Tank you in advance.
Because of the comment.
import android.content.Context;
import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.ListView;
public class FixedListView extends ListView{
final static String LOGTAG = "TEST";
private Context mContext;
VelocityTracker mVelocityTracker;
public FixedListView(Context context) {
super(context);
mContext = context;
setDivider(null);
// TODO Auto-generated constructor stub
}
#Override
public boolean onTouchEvent(MotionEvent ev)
{
boolean onTouchEvent = true;
final int action = ev.getAction();
//Add the event to the ModifiedVelocityTracker used for detect if if the motion is a fling (also creates it)
Log.d(LOGTAG, "Before add to Traker: " + SystemClock.uptimeMillis());
/*if (mVelocityTracker == null)
{
mVelocityTracker = ModifiedVelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);*/
Log.d(LOGTAG, "After add to Traker: " + SystemClock.uptimeMillis());
//Detect the event action type
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
{
Log.d(LOGTAG, "After eval action Traker: " + SystemClock.uptimeMillis());
onTouchEvent = super.onTouchEvent(ev);
Log.d(LOGTAG, "After super Y = " + ev.getY() +": " + SystemClock.uptimeMillis());
break;
}
case MotionEvent.ACTION_UP:
{
try
{
//Calculetes the velocity if the movement
//NOTE: I'm using my own VelocityTraker because the Android.Utils
//one can only be used once at a time and the superclass uses it.
/*final ModifiedVelocityTracker ModifiedVelocityTracker = mVelocityTracker;
ModifiedVelocityTracker.computeCurrentVelocity(1000);
int initialVelocity = (int)ModifiedVelocityTracker.getYVelocity();*/
if (mVelocityTracker == null)
{
mVelocityTracker = VelocityTracker.obtain();
}
final VelocityTracker ModifiedVelocityTracker = mVelocityTracker;
ModifiedVelocityTracker.computeCurrentVelocity(1000);
int initialVelocity = (int)ModifiedVelocityTracker.getYVelocity();
//Detects if the motion is a fling
if ((Math.abs(initialVelocity) >
ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) &&
(getChildCount() > 0)) // TODO FLing
{
super.onTouchEvent(getEventWithACTION_MOVE(ev));
//Send the events to cancel the superclass fling
//MotionEvent me[] = mVelocityTracker.getTrickEvents();
MotionEvent me[] = getTrickEvents(ev);
for(int i=0;i<me.length;i++)
super.onTouchEvent(me[i]);
int firstVisiblePosition = getFirstVisiblePosition();
// Pint y=0 is located on the bottom, so a negative speed means
//the finger moved up and the picture to come is the one under
if(initialVelocity < 0) //EventUp-GoDown
{
if(firstVisiblePosition != getCount() - 1) //Can Move
{
//smoothScrollToPosition(firstVisiblePosition + 1);
View currentTopView = getChildAt(firstVisiblePosition);
int botom = currentTopView.getBottom();
botom = botom - firstVisiblePosition * getHeight();
smoothScrollBy(-botom, 500);
} else
{
smoothScrollToPosition(firstVisiblePosition);
}
} else //EventDown-GoUp
{
smoothScrollToPosition(firstVisiblePosition);
}
}
else // TODO Stay in the picture that has a bigger area shown
{
onTouchEvent = super.onTouchEvent(ev);
//Center The View after the parent stops moving it
/*
* FIREST VIEW |****| / SECOND VIEW | |
*
* CASE 1 Bottom over center - Set second view the main one
* |****|
* |****| Bottom of Top View at First visible Position
* | |
* |----| center line
* | |
* | |
* | |
*
* CASE 2 Bottom under center - Set first view the main one
* |****|
* |****|
* |****|
* |----| center line
* |****|
* |****| Bottom of Top View at First visible Position
* | |
* */
int firstVisiblePosition = getFirstVisiblePosition();
View currentTopView = getChildAt(firstVisiblePosition);
int botom;
if(currentTopView != null)
{
botom = currentTopView.getBottom();
}else{
currentTopView = getSelectedView();
if(firstVisiblePosition == getPositionForView(currentTopView))
{
botom = currentTopView.getBottom();
}
else
{
botom = currentTopView.getTop();
}
}
int center = getHeight()/2;
botom = botom - firstVisiblePosition * getHeight();
if(botom < center) //Case 1 - Scroll Down
{
//Checks if the top view is the last one.
//Shouldn't happen, but just in case.
if(firstVisiblePosition != getCount() - 1) //Can Move
{
smoothScrollToPosition(firstVisiblePosition + 1);
} else
{
smoothScrollToPosition(firstVisiblePosition);
}
}
else //Case 2
{
smoothScrollToPosition(firstVisiblePosition);
}
}
onTouchEvent = true;
} catch(NullPointerException e)
{
e.printStackTrace();
}
if(mVelocityTracker != null)
mVelocityTracker.recycle();
break;
}
case MotionEvent.ACTION_CANCEL:
{
try
{
onTouchEvent = super.onTouchEvent(ev);
//Center The View after the parent stops moving it
/*
* FIREST VIEW |****| / SECOND VIEW | |
*
* CASE 1 Bottom over center - Set second view the main one
* |****|
* |****| Bottom of Top View at First visible Position
* | |
* |----| center line
* | |
* | |
* | |
*
* CASE 2 Bottom under center - Set first view the main one
* |****|
* |****|
* |****|
* |----| center line
* |****|
* |****| Bottom of Top View at First visible Position
* | |
* */
int firstVisiblePosition = getFirstVisiblePosition();
View currentTopView = getChildAt(firstVisiblePosition);
int center = getHeight()/2;
if(currentTopView.getBottom() < center) //Case 1 - Scroll Down
{
//Checks if the top view is the last one.
//Shouldn't happen, but just in case.
if(firstVisiblePosition != getCount() - 1) //Can Move
{
smoothScrollToPosition(firstVisiblePosition + 1);
} else
{
smoothScrollToPosition(firstVisiblePosition);
}
}
else //Case 2
{
if(firstVisiblePosition != 0) //Can Move
{
smoothScrollToPosition(firstVisiblePosition - 1);
} else
{
smoothScrollToPosition(firstVisiblePosition);
}
}
onTouchEvent = true;
} catch(NullPointerException e)
{
e.printStackTrace();
}
if(mVelocityTracker != null)
{
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
}
// TODO Auto-generated method stub
return onTouchEvent;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
return super.onInterceptTouchEvent(ev);
}
public MotionEvent[] getTrickEvents(MotionEvent ev)
{
//Detect the last touched position
final float requiredX = ev.getX();
final float requiredY = ev.getY();
//Get a time value that is longer than the last added event value by a bigger
//number than the LONGEST_PAST_TIME accepted by the VelocityTraker
//NOTE: If GOOGLE changes the LONGEST_PAST_TIME, we will have to change it too,
//I wasn't able to retrieve the original VelocityTracker LONGEST_PAST_TIME directly from it.
final long requiredPastTime = ev.getEventTime() + 201;
//Create the MotionEvents (Simulating no movement in y).
MotionEvent m1 = null;
if(requiredX == 0) //If at the left border, move one pixel to the right.
{
m1 = MotionEvent.obtain(requiredPastTime, requiredPastTime,
MotionEvent.ACTION_MOVE,
requiredX + 1, requiredY, 0);
}
else //If not at the left border, move one pixel to the left
{
m1 = MotionEvent.obtain(requiredPastTime, requiredPastTime,
MotionEvent.ACTION_MOVE,
requiredX - 1, requiredY, 0);
}
//Return to the original position after 100 time units
MotionEvent m2 = MotionEvent.obtain(requiredPastTime + 100, requiredPastTime + 100,
MotionEvent.ACTION_UP,
requiredX, requiredY, 0);
MotionEvent motEvents[] = {m1,m2};
return motEvents;
}
public MotionEvent getEventWithACTION_MOVE(MotionEvent ev)
{
//Detect the last touched position
final float requiredX = ev.getX();
final float requiredY = ev.getY();
//Get a time value that is longer than the last added event value by a bigger
//number than the LONGEST_PAST_TIME accepted by the VelocityTraker
//NOTE: If GOOGLE changes the LONGEST_PAST_TIME, we will have to change it too,
//I wasn't able to retrieve the original VelocityTracker LONGEST_PAST_TIME directly from it.
final long Time = ev.getEventTime();
//Create the MotionEvent
MotionEvent m1 = MotionEvent.obtain(Time, Time,
MotionEvent.ACTION_MOVE,
requiredX , requiredY, 0);
return m1;
}
}
And the adapter
public class FixedListAdapter extends BaseAdapter
{
int pictures[];
public FixedListAdapter(int pictures[])
{
this.pictures = pictures;
}
public int getCount() {
return pictures.length;
}
public Object getItem(int position) {
return pictures[position];
}
public long getItemId(int position) {
return pictures[position];
}
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout l = new LinearLayout(GalleryTest.this);
l.setLayoutParams(new ListView.LayoutParams(
ListView.LayoutParams.MATCH_PARENT, ListView.LayoutParams.MATCH_PARENT));
l.setGravity(Gravity.CENTER);
l.setPadding(0,0,0,0);
l.setBackgroundColor(Color.BLACK);
ImageView i = new ImageView(GalleryTest.this);
i.setLayoutParams(new LinearLayout.LayoutParams(width, height));
i.setScaleType(ImageView.ScaleType.FIT_CENTER);
i.setImageResource(pictures[position]);
l.addView(i);
return l;
}
}
NOTE: The picture changes on the device and the getFirstVisiblePosition() gives me the index of the first picture being shown. The problem is that getChildAt returns null with a view that is actually on screen.
Solved my problem.
I didn't find the reason for the problem, but changing the code a little bit I got to trick the problem.
int firstVisiblePosition = getFirstVisiblePosition();
View currentTopView = getChildAt(firstVisiblePosition);
int transpose = 0;
while(currentTopView == null)
{
transpose++;
currentTopView = getChildAt(firstVisiblePosition - transpose);
}
int botom = currentTopView.getBottom();
int top = currentTopView.getTop();
int height = getHeight();
botom = botom + height * (transpose - firstVisiblePosition);
top = top + height * (transpose - firstVisiblePosition);
What I did was to check if the view was null (even if it was being shown). If it was null I checked if the view over it was null, an so on until i go one that wasnt null on getChildAt(position).
When i found it, I used its position to calculate the position of the other view.
I also changed the code a little bit to use SmoothScrollBy insted os SmoothScrollToPosition because it made the view got perfectly centerded this way.
Now, If someone ever finds the reason for the problem, please tell me why its happening.
Related
I want to implement a layout where I can drag an image and drop it to another screen similar to android launcher where we can place the app icons anywhere on a set of screens scrolling horizontally. I am not sure how to start and where to start. I am thinking of implementing a layout that would be larger than screen and then start autoscrolling as soon as the user touches the image.The position of dropping the image will be fixed. Any references or better approach to implement this?
I just wrote this: (its working for the time being)
Just addDragListener only on ViewPager. You don't need to addDragListener on gridviews.
Get Position of Drop on target Grid.
Keep track of source GridView and Target Gridview and use to get its adapter.
then Manipulate on Drop between adapter.
public void setDragListener() {
pager = getViewPager();
dragListener = new View.OnDragListener() {
GridViewAdapter sourceAdapter = null;
GridViewAdapter targetAdapter = null;
int currentOffset = 0;
#Override
public boolean onDrag(View v, DragEvent event) {
int currentX = -1;
int currentY = -1;
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
if (!pager.isFakeDragging()) {
pager.beginFakeDrag();
}
break;
case DragEvent.ACTION_DRAG_ENTERED:
pager.setCurrentItem(pager.getCurrentItem());
currentOffset = (int) (event.getX());
if (!pager.isFakeDragging()) {
pager.beginFakeDrag();
}
break;
case DragEvent.ACTION_DRAG_EXITED:
break;
case DragEvent.ACTION_DRAG_LOCATION:
if (pager != null) {
if (!pager.isFakeDragging()) {
pager.beginFakeDrag();
}
if (pager.isFakeDragging()) {
int maxWidth = pager.getWidth() * pager.getCount();
int offset = (int) (event.getX());
int scrollX = getScrollX();
int dragBy = (int) -1 * (offset - currentOffset);
currentOffset = offset;
pager.fakeDragBy(dragBy * pager.getCount());
int scrolledPage = (int) (offset + scrollX) / pager.getWidth();
if (pager.getCurrentItem() != scrolledPage) {
pager.setCurrentItem(Math.abs(scrolledPage));
}
}
}
break;
case DragEvent.ACTION_DRAG_ENDED:
if (pager.isFakeDragging()) {
pager.endFakeDrag();
}
break;
case DragEvent.ACTION_DROP:
if (pager.isFakeDragging()) {
pager.endFakeDrag();
}
currentX = (int) event.getX();
currentY = (int) event.getY();
// u can use meta data and other info which u passed when longPress
// u can set sourcePage and sourcePosition in meta data.
metaMove = event.getLocalState();
sourceAdapter = // get Source Gridviews adapter
targetAdapter = // get Target GridViews Adapter ie current Page's gridviews adapter.
GridView g = // get The gridview current Page gridview
if (g != null) {
int position = g.pointToPosition(currentX + (getScrollX() - (pager.getWidth() * pager.getCurrentItem())), currentY);
metaMove.targetPosition = position;
metaMove.targetPage = currentPage;
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
// u can move from source to target by inset and remove.
// use some broadcast function to reset if page does not have any item left while moving. Ur logic here.
move(sourceAdapter, targetAdapter, metaMove);
}
});
}
break;
default:
break;
}
return true;
}
};
pager.setOnDragListener(dragListener);
}
public int getScrollX() {
int maxWidth = pager.getWidth() * pager.getCount();
int scrollX = pager.getScrollX();
if (pager.getScrollX() < 0) {
scrollX = maxWidth + scrollX - pager.getWidth();
} else if (pager.getScrollX() == 0) {
if (pager.getCurrentItem() == (pager.getCount() - 1)) {
scrollX = maxWidth + scrollX - pager.getWidth();
}
}
return scrollX;
}
Use following onLongPress on your sourceGrid:
metaMove.sourcePage = // sourcePage;
metaMove.sourcePosition = position;
metaMove.targetPosition = -1; // to be set on drop
metaMove.targetPage = -1 // to be set on drop;
boolean dragStarted = view.startDrag(null,
myShadow,
metaMove,
0
);
Use ViewPager for multiple pages.
Use GridView (in fragment) on ViewPager on each viewpager page.
setOnDragListener on viewPager only once // important
setOnDragListener on each GridView on pages// important
get position = pointToPosition(event.getX(),event.getY()) on target GridView on ACTION_DROP (add here ur icon or what ever u r moving).
ACTION_DRAG_END to use for removing from the source GridView.
use ACTION_DRAG_EXIT from gridView to change viewPager page to & fro.
Note : I assume you are using gridView.setOnItemLongClickListener() for startDrag() etc. Refer it on android site.
Here I need a gallery like view with only three images to be shown at a time on screen. In this the middle image will be larger than the two other images on its sides.
If the user scrolls the view next images will slide on screen as it does in gallery and at a time only three images will be shown out of which the center image should automatically zoom when it is shown on screen and remaining two should be smaller than it.
Here I can't use gallery because it is depreciated in android.
I was able to make a gallery like view with help of viewpager using code on this link. It shows only three images on screen at a time, which fits my one requirement. But i am not able to get the central image that is visible on screen and zoom it. Although I was able to get the clicked image on screen.
Can someone please tell me where do I need to modify this code and what I need to add in it to get the image that is in center from the images shown on screen and zoom it.
I know that there is no center image on screen according to viewpager and it is just showing three images on screen at a time because of modifications in code.
I have also tried:-
GridView with horizontal scroll
HorizontalScrollView with horizontal linear layout
but viewpager seems to be a better solution, because it stops the scrolling with only three items on screen because of viewpager's inherent properties.
and If someone knows any other method to achieve it, please tell me and I'll try it.
P.S. For anyone who wants the full code, I have added it as an answer, which has zoom capability also. Just few additions in accepted answer. :)
Following code will help you to make a gallery like view which will have center lock. It responds to touch and swipe both. It shows three images on the screen at a time and the center image is zoomed.
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
public class CenteringHorizontalScrollView extends HorizontalScrollView implements View.OnTouchListener {
private Context mContext;
private static final int SWIPE_PAGE_ON_FACTOR = 10;
private int mActiveItem;
private float mPrevScrollX;
private boolean mStart;
private int mItemWidth;
View targetLeft, targetRight;
ImageView leftImage, rightImage;
public CenteringHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext=context;
mItemWidth = 100; // or whatever your item width is.
setOnTouchListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getRawX();
boolean handled = false;
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
if (mStart) {
mPrevScrollX = x;
mStart = false;
}
break;
case MotionEvent.ACTION_UP:
mStart = true;
int minFactor = mItemWidth / SWIPE_PAGE_ON_FACTOR;
if ((mPrevScrollX - (float) x) > minFactor) {
if (mActiveItem < getMaxItemCount() - 1) {
mActiveItem = mActiveItem + 1;
}
}else if (((float) x - mPrevScrollX) > minFactor) {
if (mActiveItem > 0) {
mActiveItem = mActiveItem - 1;
}
}
scrollToActiveItem();
handled = true;
break;
}
return handled;
}
private int getMaxItemCount() {
return ((LinearLayout) getChildAt(0)).getChildCount();
}
private LinearLayout getLinearLayout() {
return (LinearLayout) getChildAt(0);
}
/**
* Centers the current view the best it can.
*/
public void centerCurrentItem() {
if (getMaxItemCount() == 0)
return;
int currentX = getScrollX();
View targetChild;
int currentChild = -1;
do {
currentChild++;
targetChild = getLinearLayout().getChildAt(currentChild);
} while (currentChild < getMaxItemCount() && targetChild.getLeft() < currentX);
if (mActiveItem != currentChild) {
mActiveItem = currentChild;
scrollToActiveItem();
}
}
/**
* Scrolls the list view to the currently active child.
*/
private void scrollToActiveItem() {
int maxItemCount = getMaxItemCount();
if (maxItemCount == 0)
return;
int targetItem = Math.min(maxItemCount - 1, mActiveItem);
targetItem = Math.max(0, targetItem);
mActiveItem = targetItem;
// Scroll so that the target child is centered
View targetView = getLinearLayout().getChildAt(targetItem);
ImageView centerImage = (ImageView)targetView;
int height=300;//set size of centered image
LinearLayout.LayoutParams flparams = new LinearLayout.LayoutParams(height, height);
centerImage.setLayoutParams(flparams);
//get the image to left of the centered image
if((targetItem-1)>=0){
targetLeft = getLinearLayout().getChildAt(targetItem-1);
leftImage = (ImageView)targetLeft;
int width=250;//set the size of left image
LinearLayout.LayoutParams leftParams = new LinearLayout.LayoutParams(width,width);
leftParams.setMargins(0, 30, 0, 0);
leftImage.setLayoutParams(leftParams);
}
//get the image to right of the centered image
if((targetItem+1)<maxItemCount){
targetRight = getLinearLayout().getChildAt(targetItem+1);
rightImage = (ImageView)targetRight;
int width=250;//set the size of right image
LinearLayout.LayoutParams rightParams = new LinearLayout.LayoutParams(width,width);
rightParams.setMargins(0, 30, 0, 0);
rightImage.setLayoutParams(rightParams);
}
int targetLeft = targetView.getLeft();
int childWidth = targetView.getRight() - targetLeft;
int width = getWidth() - getPaddingLeft() - getPaddingRight();
int targetScroll = targetLeft - ((width - childWidth) / 2);
super.smoothScrollTo(targetScroll, 0);
}
/**
* Sets the current item and centers it.
* #param currentItem The new current item.
*/
public void setCurrentItemAndCenter(int currentItem) {
mActiveItem = currentItem;
scrollToActiveItem();
}
}
In your xml add the horizontal scroll view like follow:-
<com.yourpackagename.CenteringHorizontalScrollView
android:id="#+id/HSVImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/Horizontalalternative">
<LinearLayout
android:id="#+id/linearImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
</LinearLayout>
</com.yourpackagename.CenteringHorizontalScrollView>
Define a Linear layout in your activity.
LinearLayout imageGallery;
Then get it as follows:-
imageGallery=(LinearLayout)findViewById(R.id.linearImage);
Now you have to add imageView to your LinearLayout. Here I assume that you have images in your drawable folder and you have made an array of ids of your images that you want to add to gallery. So you can do it via following method in your activity:-
for(int i=0; i<lengthOfImageIdArray; i++){
ImageView image=new ImageView(YourActivityName.this);
image.setBackgroundResource(yourArrayName[i]);
imageGallery.addView(image);
}
You can also set the width of images dynamically, so that they fit every screen, with only little extra effort.
Override setPrimaryItem in your ViewPager and make the center item bigger.
What was the issue with using a HorizontalScrollView with a LinearLayout? If it's centering you may be able to do something similar to this (assuming you've
/**
* A centering HSV loosely based on http://iotasol.blogspot.com/2011/08/creating-custom-horizontal-scroll-view.html
*/
public class CenteringHorizontalScrollView extends HorizontalScrollView implements View.OnTouchListener {
private static final int SWIPE_PAGE_ON_FACTOR = 10;
private int mActiveItem;
private float mPrevScrollX;
private boolean mStart;
private int mItemWidth;
public CenteringHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mItemWidth = 100; // or whatever your item width is.
setOnTouchListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getRawX();
boolean handled = false;
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
if (mStart) {
mPrevScrollX = x;
mStart = false;
}
break;
case MotionEvent.ACTION_UP:
mStart = true;
int minFactor = mItemWidth / SWIPE_PAGE_ON_FACTOR;
if ((mPrevScrollX - (float) x) > minFactor) {
if (mActiveItem < getMaxItemCount() - 1) {
mActiveItem = mActiveItem + 1;
}
}
else if (((float) x - mPrevScrollX) > minFactor) {
if (mActiveItem > 0) {
mActiveItem = mActiveItem - 1;
}
}
scrollToActiveItem();
handled = true;
break;
}
return handled;
}
private int getMaxItemCount() {
return ((LinearLayout) getChildAt(0)).getChildCount();
}
private LinearLayout getLinearLayout() {
return (LinearLayout) getChildAt(0);
}
/**
* Centers the current view the best it can.
*/
public void centerCurrentItem() {
if (getMaxItemCount() == 0) {
return;
}
int currentX = getScrollX();
View targetChild;
int currentChild = -1;
do {
currentChild++;
targetChild = getLinearLayout().getChildAt(currentChild);
} while (currentChild < getMaxItemCount() && targetChild.getLeft() < currentX);
if (mActiveItem != currentChild) {
mActiveItem = currentChild;
scrollToActiveItem();
}
}
/**
* Scrolls the list view to the currently active child.
*/
private void scrollToActiveItem() {
int maxItemCount = getMaxItemCount();
if (maxItemCount == 0) {
return;
}
int targetItem = Math.min(maxItemCount - 1, mActiveItem);
targetItem = Math.max(0, targetItem);
mActiveItem = targetItem;
// Scroll so that the target child is centered
View targetView = getLinearLayout().getChildAt(targetItem);
int targetLeft = targetView.getLeft();
int childWidth = targetView.getRight() - targetLeft;
int width = getWidth() - getPaddingLeft() - getPaddingRight();
int targetScroll = targetLeft - ((width - childWidth) / 2);
super.smoothScrollTo(targetScroll, 0);
}
/**
* Sets the current item and centers it.
* #param currentItem The new current item.
*/
public void setCurrentItemAndCenter(int currentItem) {
mActiveItem = currentItem;
scrollToActiveItem();
}
}
I have 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);
}
}
To understand this question, first read how this method works.
I am trying to implements a drag and drop ListView, it's going alright but have run into
a road block. So I don't have to handled everything, I am intercepting(but returning false) MotionEvents sent to the ListView, letting it handle scrolling and stuff. When I want to start dragging a item, I then return true and handled all the dragging stuff. Everything is working fine except for one thing. The drag(drag and drop) is started when it is determined that a long press as a occurred(in onInterceptTouchEvent). I get the Bitmap for the image that I drag around like so. itemPositition being the index of the item that was selected.
(omitting irrelevant parts)
...
View dragItem = mListView.getChildAt(itemPosition);
dragItem.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(dragItem.getDrawingCache());
mDragImage = new ImageView(mContext);
mDragImage.setImageBitmap(bitmap);
...
The problem is, mDragImage is a solid black like this.
But, if I don't let ListView handle anything. As in, I start the drag on ACTION_DOWN and stop on ACTION_UP, mDragImage looks has expected(but I obviously lose scrolling abilities).
Since the drag is started with a long press, the ListView is given the opportunity to do things before the long press occurs. This is my guess as to why this is happening. When a item is pressed, it is highlighted by the ListView. Somewhere in doing so, it is messing with the bitmap. So when I go to get it, it's in a weird state(all black).
I see two options for fixing this, neither of which I know how to do.
Create a image from scratch.
Handle the highlighting myself(if that is the problem).
Option two seems a better one to me, except that I looked at the documentation and the source code and could not find out how to do so. Here are some things I have done/tried.
I set setOnItemClickListener(...) and
setOnItemSelectedListener(...) with a empty method(highlighting
still happens). (Before anyone suggests it, calling
setOnClickListener results in a runtime error.)
I also looked into trying to get the ListView to make a new item
(for option 2), but could not find a way.
Spent 45ish minutes looking through the source code and
documentation trying to pinpoint where the highlighting was
happening(I never found it).
Any help fixing this would be appreciated.
(EDIT1 START)
So I don't actually know if onLongClickListener is working, I made an error before thinking it was. I am trying to set it up right now, will update when I find out if it does.
(EDIT1 END)
Last minute edit before post. I tried using onLongClickListener just now, and the image is good. I would still like to know if there is another way. How I have to use onLongClickListener to get things working is ugly, but it works. I also spent so much time trying to figure this out, it would be nice to find out the answer. I still want to be able to change/handle the highlight color, the default orangeish color is not pretty. Oh and sorry about the length of the post. I could not think of way of making it shorter, while supplying all the information I thought was needed.
use this code, it's allows operation drug and drop in ListView:
public class DraggableListView extends ListView {
private static final String LOG_TAG = "tasks365";
private static final int END_OF_LIST_POSITION = -2;
private DropListener mDropListener;
private int draggingItemHoverPosition;
private int dragStartPosition; // where was the dragged item originally
private int mUpperBound; // scroll the view when dragging point is moving out of this bound
private int mLowerBound; // scroll the view when dragging point is moving out of this bound
private int touchSlop;
private Dragging dragging;
private GestureDetector longPressDetector;
public DraggableListView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.listViewStyle);
}
public DraggableListView(final Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
longPressDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() {
#Override
public void onLongPress(final MotionEvent e) {
int x = (int) e.getX();
final int y = (int) e.getY();
int itemnum = pointToPosition(x, y);
if (itemnum == AdapterView.INVALID_POSITION) {
return;
}
if (dragging != null) {
dragging.stop();
dragging = null;
}
final View item = getChildAt(itemnum - getFirstVisiblePosition());
item.setPressed(false);
dragging = new Dragging(getContext());
dragging.start(y, ((int) e.getRawY()) - y, item);
draggingItemHoverPosition = itemnum;
dragStartPosition = draggingItemHoverPosition;
int height = getHeight();
mUpperBound = Math.min(y - touchSlop, height / 3);
mLowerBound = Math.max(y + touchSlop, height * 2 / 3);
}
});
setOnItemLongClickListener(new OnItemLongClickListener() {
#SuppressWarnings("unused")
public boolean onItemLongClick(AdapterView<?> paramAdapterView, View paramView, int paramInt, long paramLong) {
// Return true to let AbsListView reset touch mode
// Without this handler, the pressed item will keep highlight.
return true;
}
});
}
/* pointToPosition() doesn't consider invisible views, but we need to, so implement a slightly different version. */
private int myPointToPosition(int x, int y) {
if (y < 0) {
return getFirstVisiblePosition();
}
Rect frame = new Rect();
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
child.getHitRect(frame);
if (frame.contains(x, y)) {
return getFirstVisiblePosition() + i;
}
}
if ((x >= frame.left) && (x < frame.right) && (y >= frame.bottom)) {
return END_OF_LIST_POSITION;
}
return INVALID_POSITION;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (longPressDetector.onTouchEvent(ev)) {
return true;
}
if ((dragging == null) || (mDropListener == null)) {
// it is not dragging, or there is no drop listener
return super.onTouchEvent(ev);
}
int action = ev.getAction();
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
dragging.stop();
dragging = null;
if (mDropListener != null) {
if (draggingItemHoverPosition == END_OF_LIST_POSITION) {
mDropListener.drop(dragStartPosition, getCount() - 1);
} else if (draggingItemHoverPosition != INVALID_POSITION) {
mDropListener.drop(dragStartPosition, draggingItemHoverPosition);
}
}
resetViews();
break;
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
int x = (int) ev.getX();
int y = (int) ev.getY();
dragging.drag(x, y);
int position = dragging.calculateHoverPosition();
if (position != INVALID_POSITION) {
if ((action == MotionEvent.ACTION_DOWN) || (position != draggingItemHoverPosition)) {
draggingItemHoverPosition = position;
doExpansion();
}
scrollList(y);
}
break;
}
return true;
}
private void doExpansion() {
int expanItemViewIndex = draggingItemHoverPosition - getFirstVisiblePosition();
if (draggingItemHoverPosition >= dragStartPosition) {
expanItemViewIndex++;
}
// Log.v(LOG_TAG, "Dragging item hovers over position " + draggingItemHoverPosition + ", expand item at index "
// + expanItemViewIndex);
View draggingItemOriginalView = getChildAt(dragStartPosition - getFirstVisiblePosition());
for (int i = 0;; i++) {
View itemView = getChildAt(i);
if (itemView == null) {
break;
}
ViewGroup.LayoutParams params = itemView.getLayoutParams();
int height = LayoutParams.WRAP_CONTENT;
if (itemView.equals(draggingItemOriginalView)) {
height = 1;
} else if (i == expanItemViewIndex) {
height = itemView.getHeight() + dragging.getDraggingItemHeight();
}
params.height = height;
itemView.setLayoutParams(params);
}
}
/**
* Reset view to original height.
*/
private void resetViews() {
for (int i = 0;; i++) {
View v = getChildAt(i);
if (v == null) {
layoutChildren(); // force children to be recreated where needed
v = getChildAt(i);
if (v == null) {
break;
}
}
ViewGroup.LayoutParams params = v.getLayoutParams();
params.height = LayoutParams.WRAP_CONTENT;
v.setLayoutParams(params);
}
}
private void resetScrollBounds(int y) {
int height = getHeight();
if (y >= height / 3) {
mUpperBound = height / 3;
}
if (y <= height * 2 / 3) {
mLowerBound = height * 2 / 3;
}
}
private void scrollList(int y) {
resetScrollBounds(y);
int height = getHeight();
int speed = 0;
if (y > mLowerBound) {
// scroll the list up a bit
speed = y > (height + mLowerBound) / 2 ? 16 : 4;
} else if (y < mUpperBound) {
// scroll the list down a bit
speed = y < mUpperBound / 2 ? -16 : -4;
}
if (speed != 0) {
int ref = pointToPosition(0, height / 2);
if (ref == AdapterView.INVALID_POSITION) {
//we hit a divider or an invisible view, check somewhere else
ref = pointToPosition(0, height / 2 + getDividerHeight() + 64);
}
View v = getChildAt(ref - getFirstVisiblePosition());
if (v != null) {
int pos = v.getTop();
setSelectionFromTop(ref, pos - speed);
}
}
}
public void setDropListener(DropListener l) {
mDropListener = l;
}
public interface DropListener {
void drop(int from, int to);
}
class Dragging {
private Context context;
private WindowManager windowManager;
private WindowManager.LayoutParams mWindowParams;
private ImageView mDragView;
private Bitmap mDragBitmap;
private int coordOffset;
private int mDragPoint; // at what offset inside the item did the user grab it
private int draggingItemHeight;
private int x;
private int y;
private int lastY;
public Dragging(Context context) {
this.context = context;
windowManager = (WindowManager) context.getSystemService("window");
}
/**
* #param y
* #param offset - the difference in y axis between screen coordinates and coordinates in this view
* #param view - which view is dragged
*/
public void start(int y, int offset, View view) {
this.y = y;
lastY = y;
this.coordOffset = offset;
mDragPoint = y - view.getTop();
draggingItemHeight = view.getHeight();
mDragView = new ImageView(context);
mDragView.setBackgroundResource(android.R.drawable.alert_light_frame);
// Create a copy of the drawing cache so that it does not get recycled
// by the framework when the list tries to clean up memory
view.setDrawingCacheEnabled(true);
mDragBitmap = Bitmap.createBitmap(view.getDrawingCache());
mDragView.setImageBitmap(mDragBitmap);
mWindowParams = new WindowManager.LayoutParams();
mWindowParams.gravity = Gravity.TOP;
mWindowParams.x = 0;
mWindowParams.y = y - mDragPoint + coordOffset;
mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
mWindowParams.format = PixelFormat.TRANSLUCENT;
mWindowParams.windowAnimations = 0;
windowManager.addView(mDragView, mWindowParams);
}
public void drag(int x, int y) {
lastY = this.y;
this.x = x;
this.y = y;
mWindowParams.y = y - mDragPoint + coordOffset;
windowManager.updateViewLayout(mDragView, mWindowParams);
}
public void stop() {
if (mDragView != null) {
windowManager.removeView(mDragView);
mDragView.setImageDrawable(null);
mDragView = null;
}
if (mDragBitmap != null) {
mDragBitmap.recycle();
mDragBitmap = null;
}
}
public int getDraggingItemHeight() {
return draggingItemHeight;
}
public int calculateHoverPosition() {
int adjustedY = (int) (y - mDragPoint + (Math.signum(y - lastY) + 2) * draggingItemHeight / 2);
// Log.v(LOG_TAG, "calculateHoverPosition(): lastY=" + lastY + ", y=" + y + ", adjustedY=" + adjustedY);
int pos = myPointToPosition(0, adjustedY);
if (pos >= 0) {
if (pos >= dragStartPosition) {
pos -= 1;
}
}
return pos;
}
}
}
I'm interested in creating a horizontal scroll view that "snaps" to the viewed item, so only one item is ever shown at a time. The user can touch-drag left/right and will see previous/next views, switching to it if there's enough velocity. This interaction is exactly like what the new weather/news widget that comes with the Nexus One does for navigating between its "tabs".
Are there any existing view widgets that do this?
Update: found a copy of the news/weather widget (GenieWidget) and they seem to have implemented their own widget to accomplish this which they call com.google.android.apps.genie.geniewidget.ui.FlingableLinearLayout which is part of their own custom com.google.android.apps.genie.geniewidget.ui.TabView. As that source isn't available, that's not looking too hopeful a direction.
(update 20110905: Official android tools now do this better)
I cloned Eric Taix's http://code.google.com/p/andro-views/ on github
https://github.com/olibye/AndroViews
Then applied the patches from above:
JonO's patch
Tom de Waard's patch
Split into a library and an example, allowing simple inclusion in other projects
I would have made this comment above, however I didn't appear able to comment on JonO's answer
Don't look at the News and weather implementation, it has a couple of flaws. You can however use the source code of the Home app (called Launcher or Launcher2), at android.git.kernel.org. The widget we use to do the scrolling on Home is in Workspace.java.
Eric Taix has done most of the grunt work of stripping the Workspace into a WorkspaceView that can be reused. It can be found here: http://code.google.com/p/andro-views/
The version as of posting does what it's supposed to in the emulator, but on real hardware it sometimes gets stuck between views instead of snapping back--I have emailed him a patch for this (which he is testing before committing, as of the date of posting this) that should make it behave exactly as the Workspace does.
If the patch doesn't appear there shortly, I will post it separately.
As promised, since it hasn't yet appeared, here is my patched version:
/**
* Copyright 2010 Eric Taix (eric.taix#gmail.com) Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and limitations under the
* License.
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.Interpolator;
import android.widget.Scroller;
/**
* The workspace is a wide area with a infinite number of screens. Each screen contains a view. A workspace is meant to
* be used with a fixed width only.<br/>
* <br/>
* This code has been done by using com.android.launcher.Workspace.java
*/
public class WorkspaceView extends ViewGroup {
private static final int INVALID_POINTER = -1;
private int mActivePointerId = INVALID_POINTER;
private static final int INVALID_SCREEN = -1;
// The velocity at which a fling gesture will cause us to snap to the next screen
private static final int SNAP_VELOCITY = 500;
// the default screen index
private int defaultScreen;
// The current screen index
private int currentScreen;
// The next screen index
private int nextScreen = INVALID_SCREEN;
// Wallpaper properties
private Bitmap wallpaper;
private Paint paint;
private int wallpaperWidth;
private int wallpaperHeight;
private float wallpaperOffset;
private boolean wallpaperLoaded;
private boolean firstWallpaperLayout = true;
private static final int TAB_INDICATOR_HEIGHT_PCT = 2;
private RectF selectedTab;
// The scroller which scroll each view
private Scroller scroller;
// A tracker which to calculate the velocity of a mouvement
private VelocityTracker mVelocityTracker;
// Tha last known values of X and Y
private float lastMotionX;
private float lastMotionY;
private final static int TOUCH_STATE_REST = 0;
private final static int TOUCH_STATE_SCROLLING = 1;
// The current touch state
private int touchState = TOUCH_STATE_REST;
// The minimal distance of a touch slop
private int touchSlop;
// An internal flag to reset long press when user is scrolling
private boolean allowLongPress;
// A flag to know if touch event have to be ignored. Used also in internal
private boolean locked;
private WorkspaceOvershootInterpolator mScrollInterpolator;
private int mMaximumVelocity;
private Paint selectedTabPaint;
private Canvas canvas;
private RectF bar;
private Paint tabIndicatorBackgroundPaint;
private static class WorkspaceOvershootInterpolator implements Interpolator {
private static final float DEFAULT_TENSION = 1.3f;
private float mTension;
public WorkspaceOvershootInterpolator() {
mTension = DEFAULT_TENSION;
}
public void setDistance(int distance) {
mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
}
public void disableSettle() {
mTension = 0.f;
}
public float getInterpolation(float t) {
// _o(t) = t * t * ((tension + 1) * t + tension)
// o(t) = _o(t - 1) + 1
t -= 1.0f;
return t * t * ((mTension + 1) * t + mTension) + 1.0f;
}
}
/**
* Used to inflate the Workspace from XML.
*
* #param context The application's context.
* #param attrs The attribtues set containing the Workspace's customization values.
*/
public WorkspaceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Used to inflate the Workspace from XML.
*
* #param context The application's context.
* #param attrs The attribtues set containing the Workspace's customization values.
* #param defStyle Unused.
*/
public WorkspaceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
defaultScreen = 0;
initWorkspace();
}
/**
* Initializes various states for this workspace.
*/
private void initWorkspace() {
mScrollInterpolator = new WorkspaceOvershootInterpolator();
scroller = new Scroller(getContext(),mScrollInterpolator);
currentScreen = defaultScreen;
paint = new Paint();
paint.setDither(false);
// Does this do anything for me?
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
touchSlop = configuration.getScaledTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
selectedTabPaint = new Paint();
selectedTabPaint.setColor(getResources().getColor(R.color.RED));
selectedTabPaint.setStyle(Paint.Style.FILL_AND_STROKE);
tabIndicatorBackgroundPaint = new Paint();
tabIndicatorBackgroundPaint.setColor(getResources().getColor(R.color.GRAY));
tabIndicatorBackgroundPaint.setStyle(Paint.Style.FILL);
}
/**
* Set a new distance that a touch can wander before we think the user is scrolling in pixels slop<br/>
*
* #param touchSlopP
*/
public void setTouchSlop(int touchSlopP) {
touchSlop = touchSlopP;
}
/**
* Set the background's wallpaper.
*/
public void loadWallpaper(Bitmap bitmap) {
wallpaper = bitmap;
wallpaperLoaded = true;
requestLayout();
invalidate();
}
boolean isDefaultScreenShowing() {
return currentScreen == defaultScreen;
}
/**
* Returns the index of the currently displayed screen.
*
* #return The index of the currently displayed screen.
*/
int getCurrentScreen() {
return currentScreen;
}
/**
* Sets the current screen.
*
* #param currentScreen
*/
public void setCurrentScreen(int currentScreen) {
if (!scroller.isFinished()) scroller.abortAnimation();
currentScreen = Math.max(0, Math.min(currentScreen, getChildCount()));
scrollTo(currentScreen * getWidth(), 0);
Log.d("workspace", "setCurrentScreen: width is " + getWidth());
invalidate();
}
/**
* Shows the default screen (defined by the firstScreen attribute in XML.)
*/
void showDefaultScreen() {
setCurrentScreen(defaultScreen);
}
/**
* Registers the specified listener on each screen contained in this workspace.
*
* #param l The listener used to respond to long clicks.
*/
#Override
public void setOnLongClickListener(OnLongClickListener l) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).setOnLongClickListener(l);
}
}
#Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate();
} else if (nextScreen != INVALID_SCREEN) {
currentScreen = Math.max(0, Math.min(nextScreen, getChildCount() - 1));
nextScreen = INVALID_SCREEN;
}
}
/**
* ViewGroup.dispatchDraw() supports many features we don't need: clip to padding, layout animation, animation
* listener, disappearing children, etc. The following implementation attempts to fast-track the drawing dispatch by
* drawing only what we know needs to be drawn.
*/
#Override
protected void dispatchDraw(Canvas canvas) {
// First draw the wallpaper if needed
if (wallpaper != null) {
float x = getScrollX() * wallpaperOffset;
if (x + wallpaperWidth < getRight() - getLeft()) {
x = getRight() - getLeft() - wallpaperWidth;
}
canvas.drawBitmap(wallpaper, x, (getBottom() - getTop() - wallpaperHeight) / 2, paint);
}
// Determine if we need to draw every child or only the current screen
boolean fastDraw = touchState != TOUCH_STATE_SCROLLING && nextScreen == INVALID_SCREEN;
// If we are not scrolling or flinging, draw only the current screen
if (fastDraw) {
View v = getChildAt(currentScreen);
drawChild(canvas, v, getDrawingTime());
}
else {
final long drawingTime = getDrawingTime();
// If we are flinging, draw only the current screen and the target screen
if (nextScreen >= 0 && nextScreen < getChildCount() && Math.abs(currentScreen - nextScreen) == 1) {
drawChild(canvas, getChildAt(currentScreen), drawingTime);
drawChild(canvas, getChildAt(nextScreen), drawingTime);
}
else {
// If we are scrolling, draw all of our children
final int count = getChildCount();
for (int i = 0; i < count; i++) {
drawChild(canvas, getChildAt(i), drawingTime);
}
}
}
updateTabIndicator();
canvas.drawBitmap(bitmap, getScrollX(), getMeasuredHeight()*(100-TAB_INDICATOR_HEIGHT_PCT)/100, paint);
}
/**
* Measure the workspace AND also children
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int height = MeasureSpec.getSize(heightMeasureSpec);
// Log.d("workspace","Height is " + height);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
}
// The children are given the same width and height as the workspace
final int count = getChildCount();
for (int i = 0; i < count; i++) {
int adjustedHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height*(100-TAB_INDICATOR_HEIGHT_PCT)/100, heightMode);
getChildAt(i).measure(widthMeasureSpec,adjustedHeightMeasureSpec);
}
// Compute wallpaper
if (wallpaperLoaded) {
wallpaperLoaded = false;
wallpaper = centerToFit(wallpaper, width, height, getContext());
wallpaperWidth = wallpaper.getWidth();
wallpaperHeight = wallpaper.getHeight();
}
wallpaperOffset = wallpaperWidth > width ? (count * width - wallpaperWidth) / ((count - 1) * (float) width) : 1.0f;
if (firstWallpaperLayout) {
scrollTo(currentScreen * width, 0);
firstWallpaperLayout = false;
}
// Log.d("workspace","Top is "+getTop()+", bottom is "+getBottom()+", left is "+getLeft()+", right is "+getRight());
updateTabIndicator();
invalidate();
}
Bitmap bitmap;
private OnLoadListener load;
private int lastEvHashCode;
private void updateTabIndicator(){
int width = getMeasuredWidth();
int height = getMeasuredHeight();
//For drawing in its own bitmap:
bar = new RectF(0, 0, width, (TAB_INDICATOR_HEIGHT_PCT*height/100));
int startPos = getScrollX()/(getChildCount());
selectedTab = new RectF(startPos, 0, startPos+width/getChildCount(), (TAB_INDICATOR_HEIGHT_PCT*height/100));
bitmap = Bitmap.createBitmap(width, (TAB_INDICATOR_HEIGHT_PCT*height/100), Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
canvas.drawRoundRect(bar,0,0, tabIndicatorBackgroundPaint);
canvas.drawRoundRect(selectedTab, 5,5, selectedTabPaint);
}
/**
* Overrided method to layout child
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int childLeft = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
final int childWidth = child.getMeasuredWidth();
child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
childLeft += childWidth;
}
}
load.onLoad();
}
#Override
public boolean dispatchUnhandledMove(View focused, int direction) {
if (direction == View.FOCUS_LEFT) {
if (getCurrentScreen() > 0) {
scrollToScreen(getCurrentScreen() - 1);
return true;
}
}
else if (direction == View.FOCUS_RIGHT) {
if (getCurrentScreen() < getChildCount() - 1) {
scrollToScreen(getCurrentScreen() + 1);
return true;
}
}
return super.dispatchUnhandledMove(focused, direction);
}
/**
* This method JUST determines whether we want to intercept the motion. If we return true, onTouchEvent will be called
* and we do the actual scrolling there.
*/
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("workspace","Intercepted a touch event");
if (locked) {
return true;
}
/*
* Shortcut the most recurring case: the user is in the dragging state and he is moving his finger. We want to
* intercept this motion.
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (touchState != TOUCH_STATE_REST)) {
return true;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
// switch (action & MotionEvent.ACTION_MASK) {
switch (action) {
case MotionEvent.ACTION_MOVE:
// Log.d("workspace","Intercepted a move event");
/*
* Locally do absolute value. mLastMotionX is set to the y value of the down event.
*/
handleInterceptMove(ev);
break;
case MotionEvent.ACTION_DOWN:
// Remember location of down touch
final float x1 = ev.getX();
final float y1 = ev.getY();
lastMotionX = x1;
lastMotionY = y1;
allowLongPress = true;
mActivePointerId = ev.getPointerId(0);
/*
* If being flinged and user touches the screen, initiate drag; otherwise don't. mScroller.isFinished should be
* false when being flinged.
*/
touchState = scroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mActivePointerId = INVALID_POINTER;
allowLongPress = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
touchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
}
/*
* The only time we want to intercept motion events is if we are in the drag mode.
*/
return touchState != TOUCH_STATE_REST;
}
private void handleInterceptMove(MotionEvent ev) {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
final int xDiff = (int) Math.abs(x - lastMotionX);
final int yDiff = (int) Math.abs(y - lastMotionY);
boolean xMoved = xDiff > touchSlop;
boolean yMoved = yDiff > touchSlop;
if (xMoved || yMoved) {
//Log.d("workspace","Detected move. Checking to scroll.");
if (xMoved && !yMoved) {
//Log.d("workspace","Detected X move. Scrolling.");
// Scroll if the user moved far enough along the X axis
touchState = TOUCH_STATE_SCROLLING;
lastMotionX = x;
}
// Either way, cancel any pending longpress
if (allowLongPress) {
allowLongPress = false;
// Try canceling the long press. It could also have been scheduled
// by a distant descendant, so use the mAllowLongPress flag to block
// everything
final View currentView = getChildAt(currentScreen);
currentView.cancelLongPress();
}
}
}
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >>
MotionEvent.ACTION_POINTER_ID_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
lastMotionX = ev.getX(newPointerIndex);
lastMotionY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
}
}
/**
* Track the touch event
*/
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Log.d("workspace","caught a touch event");
if (locked) {
return true;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
final float x = ev.getX();
switch (action) {
case MotionEvent.ACTION_DOWN:
//We can still get here even if we returned false from the intercept function.
//That's the only way we can get a TOUCH_STATE_REST (0) here.
//That means that our child hasn't handled the event, so we need to
// Log.d("workspace","caught a down touch event and touchstate =" + touchState);
if(touchState != TOUCH_STATE_REST){
/*
* If being flinged and user touches, stop the fling. isFinished will be false if being flinged.
*/
if (!scroller.isFinished()) {
scroller.abortAnimation();
}
// Remember where the motion event started
lastMotionX = x;
mActivePointerId = ev.getPointerId(0);
}
break;
case MotionEvent.ACTION_MOVE:
if (touchState == TOUCH_STATE_SCROLLING) {
handleScrollMove(ev);
} else {
// Log.d("workspace","caught a move touch event but not scrolling");
//NOTE: We will never hit this case in Android 2.2. This is to fix a 2.1 bug.
//We need to do the work of interceptTouchEvent here because we don't intercept the move
//on children who don't scroll.
Log.d("workspace","handling move from onTouch");
if(onInterceptTouchEvent(ev) && touchState == TOUCH_STATE_SCROLLING){
handleScrollMove(ev);
}
}
break;
case MotionEvent.ACTION_UP:
// Log.d("workspace","caught an up touch event");
if (touchState == TOUCH_STATE_SCROLLING) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocityX = (int) velocityTracker.getXVelocity();
if (velocityX > SNAP_VELOCITY && currentScreen > 0) {
// Fling hard enough to move left
scrollToScreen(currentScreen - 1);
}
else if (velocityX < -SNAP_VELOCITY && currentScreen < getChildCount() - 1) {
// Fling hard enough to move right
scrollToScreen(currentScreen + 1);
}
else {
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
touchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
break;
case MotionEvent.ACTION_CANCEL:
Log.d("workspace","caught a cancel touch event");
touchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
break;
case MotionEvent.ACTION_POINTER_UP:
Log.d("workspace","caught a pointer up touch event");
onSecondaryPointerUp(ev);
break;
}
return true;
}
private void handleScrollMove(MotionEvent ev){
// Scroll to follow the motion event
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x1 = ev.getX(pointerIndex);
final int deltaX = (int) (lastMotionX - x1);
lastMotionX = x1;
if (deltaX < 0) {
if (getScrollX() > 0) {
//Scrollby invalidates automatically
scrollBy(Math.max(-getScrollX(), deltaX), 0);
}
}
else if (deltaX > 0) {
final int availableToScroll = getChildAt(getChildCount() - 1).getRight() - getScrollX() - getWidth();
if (availableToScroll > 0) {
//Scrollby invalidates automatically
scrollBy(Math.min(availableToScroll, deltaX), 0);
}
} else {
awakenScrollBars();
}
}
/**
* Scroll to the appropriated screen depending of the current position
*/
private void snapToDestination() {
final int screenWidth = getWidth();
final int whichScreen = (getScrollX() + (screenWidth / 2)) / screenWidth;
Log.d("workspace", "snapToDestination");
scrollToScreen(whichScreen);
}
/**
* Scroll to a specific screen
*
* #param whichScreen
*/
public void scrollToScreen(int whichScreen) {
scrollToScreen(whichScreen, false);
}
private void scrollToScreen(int whichScreen, boolean immediate){
Log.d("workspace", "snapToScreen=" + whichScreen);
boolean changingScreens = whichScreen != currentScreen;
nextScreen = whichScreen;
View focusedChild = getFocusedChild();
if (focusedChild != null && changingScreens && focusedChild == getChildAt(currentScreen)) {
focusedChild.clearFocus();
}
final int newX = whichScreen * getWidth();
final int delta = newX - getScrollX();
Log.d("workspace", "newX=" + newX + " scrollX=" + getScrollX() + " delta=" + delta);
scroller.startScroll(getScrollX(), 0, delta, 0, immediate ? 0 : Math.abs(delta) * 2);
invalidate();
}
public void scrollToScreenImmediate(int whichScreen){
scrollToScreen(whichScreen, true);
}
/**
* Return the parceable instance to be saved
*/
#Override
protected Parcelable onSaveInstanceState() {
final SavedState state = new SavedState(super.onSaveInstanceState());
state.currentScreen = currentScreen;
return state;
}
/**
* Restore the previous saved current screen
*/
#Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
if (savedState.currentScreen != -1) {
currentScreen = savedState.currentScreen;
}
}
/**
* Scroll to the left right screen
*/
public void scrollLeft() {
if (nextScreen == INVALID_SCREEN && currentScreen > 0 && scroller.isFinished()) {
scrollToScreen(currentScreen - 1);
}
}
/**
* Scroll to the next right screen
*/
public void scrollRight() {
if (nextScreen == INVALID_SCREEN && currentScreen < getChildCount() - 1 && scroller.isFinished()) {
scrollToScreen(currentScreen + 1);
}
}
/**
* Return the screen's index where a view has been added to.
*
* #param v
* #return
*/
public int getScreenForView(View v) {
int result = -1;
if (v != null) {
ViewParent vp = v.getParent();
int count = getChildCount();
for (int i = 0; i < count; i++) {
if (vp == getChildAt(i)) {
return i;
}
}
}
return result;
}
/**
* Return a view instance according to the tag parameter or null if the view could not be found
*
* #param tag
* #return
*/
public View getViewForTag(Object tag) {
int screenCount = getChildCount();
for (int screen = 0; screen < screenCount; screen++) {
View child = getChildAt(screen);
if (child.getTag() == tag) {
return child;
}
}
return null;
}
/**
* Unlocks the SlidingDrawer so that touch events are processed.
*
* #see #lock()
*/
public void unlock() {
locked = false;
}
/**
* Locks the SlidingDrawer so that touch events are ignores.
*
* #see #unlock()
*/
public void lock() {
locked = true;
}
/**
* #return True is long presses are still allowed for the current touch
*/
public boolean allowLongPress() {
return allowLongPress;
}
/**
* Move to the default screen
*/
public void moveToDefaultScreen() {
scrollToScreen(defaultScreen);
getChildAt(defaultScreen).requestFocus();
}
// ========================= INNER CLASSES ==============================
/**
* A SavedState which save and load the current screen
*/
public static class SavedState extends BaseSavedState {
int currentScreen = -1;
/**
* Internal constructor
*
* #param superState
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Private constructor
*
* #param in
*/
private SavedState(Parcel in) {
super(in);
currentScreen = in.readInt();
}
/**
* Save the current screen
*/
#Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(currentScreen);
}
/**
* Return a Parcelable creator
*/
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
//Added for "flipper" compatibility
public int getDisplayedChild(){
return getCurrentScreen();
}
public void setDisplayedChild(int i){
// setCurrentScreen(i);
scrollToScreen(i);
getChildAt(i).requestFocus();
}
public void setOnLoadListener(OnLoadListener load){
this.load = load;
}
public void flipLeft(){
scrollLeft();
}
public void flipRight(){
scrollRight();
}
// ======================== UTILITIES METHODS ==========================
/**
* Return a centered Bitmap
*
* #param bitmap
* #param width
* #param height
* #param context
* #return
*/
static Bitmap centerToFit(Bitmap bitmap, int width, int height, Context context) {
final int bitmapWidth = bitmap.getWidth();
final int bitmapHeight = bitmap.getHeight();
if (bitmapWidth < width || bitmapHeight < height) {
// Normally should get the window_background color of the context
int color = Integer.valueOf("FF191919", 16);
Bitmap centered = Bitmap.createBitmap(bitmapWidth < width ? width : bitmapWidth, bitmapHeight < height ? height
: bitmapHeight, Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(centered);
canvas.drawColor(color);
canvas.drawBitmap(bitmap, (width - bitmapWidth) / 2.0f, (height - bitmapHeight) / 2.0f, null);
bitmap = centered;
}
return bitmap;
}
}
I am not aware of Nexus one but, i can suggest u the gallery view. It is perfectly apt one for your requirement according to your above explanation.
is this what are you looking for ?
http://code.google.com/p/mobyfactory-uiwidgets-android/
I tried many of the code examples we can find here and there online, and even if almost all were good, they were not handling properly what I had in mind.
In fact I have several ScrollViews side by side, the screen displaying only one. When the user scrolls vertically, I want the ScrollView to scroll; when the user scrolls horizontally, I want the ViewFlow (the global layout I'm using) to snap to the previous/next ScrollView.
I got something almost close to what I wanted, but that'll do it for now. I know that this was answered long time ago, but I want to share what I made, so here it is.
It's just a class that derives ScrollView and implements onInterceptTouchEvent and onTouchEvent.
/**
* Try to know if the move will be an horizontal drag
* If so, we want to intercept the touch event (return true)
* otherwise, we don't want to intercept this event
*/
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction() & MotionEvent.ACTION_MASK;
// If scrolling (over X or Y), we want to intercept: process
// it within the ScrollView & send it to the ViewFlow
if (action == MotionEvent.ACTION_MOVE && mState != NO_SCROLL) {
return true;
}
// Try to detect the motion
switch (action) {
case MotionEvent.ACTION_MOVE:
float deltaX = Math.abs(ev.getX()-lastX);
float deltaY = Math.abs(ev.getY()-lastY);
boolean xMoved = deltaX > 0.5* mTouchSlop && deltaX > deltaY;
boolean yMoved = deltaY > 0.5*mTouchSlop;
if (xMoved || yMoved) {
if (xMoved && !yMoved) {
mState = SCROLL_X;
} else {
mState = SCROLL_Y;
}
} else {
mState = NO_SCROLL;
}
lastX = ev.getX();
lastY = ev.getY();
break;
case MotionEvent.ACTION_DOWN:
// Remember location of down touch
lastX = ev.getX();
lastY = ev.getY();
mState = NO_SCROLL;
sendTouchEvent(ev);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mState == NO_SCROLL) {
// Thats a tap!
}
mState = NO_SCROLL;
break;
}
if (mState != SCROLL_X) {
super.onInterceptTouchEvent(ev);
}
return mState == SCROLL_X;// Intercept only dragging over X axis
}
/**
* Handles touch events. Basically only horizontal drag.
* Such events are handled locally by the scrollview
* _AND_ sent to the {#link ViewFlow} in order to make it snap
* horizontally
* #param ev the MotionEvent
*/
#Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
super.onTouchEvent(ev);
switch (action) {
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mState = NO_SCROLL;
sendTouchEvent(ev);
super.onTouchEvent(ev);
break;
case MotionEvent.ACTION_DOWN:
super.onTouchEvent(ev);
break;
case MotionEvent.ACTION_MOVE:
lastY = ev.getX();
lastY = ev.getY();
if (mState == SCROLL_X) {
sendTouchEvent(ev);
} else if (mState == SCROLL_Y) {
super.onTouchEvent(ev);
}
break;
}
return false;
}
The code is not perfect and requires a little bit more polish, but it's functional. Almost like Genie Widget. (I wish this guy was open source!) Especially around the scrolling detection, it's not using exactly the touch slop, and it's comparing the move between the X and Y axis. But this is easily tuneable.
And I am using this very good code for my ViewFlow: https://github.com/pakerfeldt/android-viewflow