I am Working on Android cocos 2d and i have 8-10 CCSprites which has to scroll horizontally by click of each sprite their is next CCLayer to load so need to Add ScrollView in CCLayer but i am not getting how to do this i am using cocos2d-android.jar
i am using this code but not working :-
final Activity mActivity=CCDirector.sharedDirector().getActivity();
final View view= LayoutInflater.from(mActivity).inflate(R.layout.level_scroll,null);
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
mActivity.addContentView(view, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
}
});
This is the layer to which you have to add all sprites:
public class ItemScrollLayer extends CCLayer{
ArrayList<ExtraProjectileData> spirtes;
float scaleX,scaleY;
public ItemScrollLayer(float scaleX,float scaleY,ArrayList<ExtraProjectileData> sprites,float screensize)
{
this.scaleX = scaleX;
this.scaleY = scaleY;
this.spirtes = sprites;
float horizontal_distance = 140*scaleX;
for(int i = 0;i<sprites.size();i++)
{
CCSprite indi_sprite = sprites.get(i).getProjectile();
indi_sprite.setScale(GameActivity.aspect_Scale(indi_sprite, scaleX, scaleY));
indi_sprite.setPosition(horizontal_distance+(i*screensize),150*scaleY);
addChild(indi_sprite);
}
}
}
the code to move layer placed in on Touch of my MainLayer:
#Override
public boolean ccTouchesMoved(MotionEvent event)
{
System.out.println("Touches Moved Called for UpgradeMenu");
CGPoint LocationMoved = CCDirector.sharedDirector().convertToGL(CGPoint.make(event.getX(), event.getY()));
float difference = LocationMoved.x - PrevTouchLocation.x;
float posX = scroll_layer.getPosition().x + difference;
scroll_layer.setPosition(CGPoint.make(posX, 0));
if(posX > 0){
scroll_layer.setPosition(CGPoint.zero());
}
else if(posX < (-size.width)*(sprites.size()-1)){
System.out.println("Right Limit Exceeded");
scroll_layer.setPosition((-size.width)*(sprites.size()-1),0);
}
if(difference < -5*GameActivity.VEL_FACTOR){
direction = -1;
}
else if(difference > 5*GameActivity.VEL_FACTOR){
direction = 1;
}
PrevTouchLocation = LocationMoved;
return true;
}
#Override
public boolean ccTouchesEnded(MotionEvent event)
{
endLocation = CCDirector.sharedDirector().convertToGL(CGPoint.make(event.getX(), event.getY()));
if(!moving)
{
float total = startLocation.x-endLocation.x;
if(direction == 1 && !(counter <=0))
{
CGPoint move_pos = CGPoint.make(size.width+total, 0);
CCMoveBy go_left = CCMoveBy.action(0.5f, move_pos);
CCCallFuncN regulator = CCCallFuncN.action(this, "regulator");
CCSequence seq = CCSequence.actions(go_left, regulator);
moving = true;
scroll_layer.runAction(seq);
counter--;
projectilePriceLabel.setString(getCurrentPrice());
}
else if(direction == -1 && !(counter >= sprites.size()-1))
{
CGPoint move_pos = CGPoint.make(-size.width+total, 0);
CCMoveBy go_right = CCMoveBy.action(0.5f, move_pos);
CCCallFuncN regulator = CCCallFuncN.action(this, "regulator");
CCSequence seq = CCSequence.actions(go_right, regulator);
moving = true;
scroll_layer.runAction(seq);
counter++;
projectilePriceLabel.setString(getCurrentPrice());
}
}
PrevTouchLocation = CGPoint.zero();
return true;
}
you can edit as per your requirement
Check this, its for vertical scrolling. You just need to change little to achieve.
https://stackoverflow.com/a/12056450/1614340
Let me know if you can't get any idea from this I will provide code.
You can do by adding all sprites to a parent layer then move this parent layer by using MoveBy modifier in ccTouchesMoved
sorry for delay. You have to initialize PrevTouchLocation before you use that as mention below.
CGPoint PrevTouchLocation = CGPoint.zero();
Related
I believe in your bright mind and strong android skills. I am little bit stuck.
I have the following situation. I've created app for learning how to work with Gestures and canvas.
Idea is simple when I single time tap on the screen and where I've tapped should appear bubble (R.drawable.bubble). If there is already some bubble application should delete it (clear space).
But, I have some difficulties with this. Place where I've tapped and where bubble actually appears have some significantly different in position.
Please give some advice where I should look. What I missed ?
Thanks in advance. Below I provide my code.
public class BubbleActivity extends Activity {
// Main view
RelativeLayout mFrame;
// Bubble image
private Bitmap mBitmap;
// gesture detector
GestureDetector mGestureDetector;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bubble);
// setup user interface
mFrame = (RelativeLayout) findViewById(R.id.frame);
// load basic bubble Bitmap
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.b128);
}
#Override
protected void onResume() {
super.onResume();
// init gesture detector
setupGestureDetector();
}
private void setupGestureDetector() {
mGestureDetector = new GestureDetector(this,
new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if(mFrame.getChildCount() == 0) {
BubbleView bubble = new BubbleView(getApplicationContext(),
e.getX(),
e.getY());
mFrame.addView(bubble);
} else {
for(int i=0; i < mFrame.getChildCount(); i++) {
BubbleView bubble = (BubbleView) mFrame.getChildAt(i);
if(bubble.intersect(e.getX(), e.getY())) {
mFrame.removeViewAt(i);
} else {
BubbleView newBubble = new BubbleView(getApplicationContext(),
e.getX(),
e.getY());
mFrame.addView(newBubble);
}
}
}
return true;
}
});
}
#Override
public boolean onTouchEvent(MotionEvent event) {
this.mGestureDetector.onTouchEvent(event);
return false;
}
private class BubbleView extends View {
private static final int BITMAP_SIZE = 64;
private float mXPos;
private float mYPos;
private Bitmap mScaledBitmap;
private int mScaledBitmapWidth;
public BubbleView(Context context, float x, float y) {
super(context);
mXPos = x;
mYPos = y;
Random r = new Random();
createScaledBitmap(r);
}
private void createScaledBitmap(Random r) {
mScaledBitmapWidth = (r.nextInt(3) + 1) * BITMAP_SIZE;
mScaledBitmap = Bitmap.createScaledBitmap(mBitmap,
mScaledBitmapWidth,
mScaledBitmapWidth,
false);
}
#Override
protected void onDraw(Canvas canvas) {
Paint mPaint = new Paint();
mPaint.setAntiAlias(true);
canvas.drawBitmap(mScaledBitmap,
this.mXPos,
this.mYPos,
mPaint);
}
public boolean intersect(float x, float y) {
if(Math.abs(this.mXPos - x) < mScaledBitmapWidth
|| Math.abs(this.mYPos - y) < mScaledBitmapWidth) {
return true;
} else {
return false;
}
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.bubble, menu);
return true;
}
}
One thing I have seen is that you should create the new BubbleView object outside your for loop.
I would use a boolean and if you don't find anyone inside the loop, then you could create one.
public boolean onSingleTapConfirmed(MotionEvent e) {
boolean found = false;
if(mFrame.getChildCount() == 0) {
BubbleView bubble = new BubbleView(getApplicationContext(),
e.getX(),
e.getY());
mFrame.addView(bubble);
} else {
for(int i=0; i < mFrame.getChildCount(); i++) {
BubbleView bubble = (BubbleView) mFrame.getChildAt(i);
if(bubble.intersect(e.getX(), e.getY())) {
mFrame.removeViewAt(i);
found = true;
break;
}
}
}
if (!found) {
BubbleView newBubble = new BubbleView(getApplicationContext(),
e.getX(),
e.getY());
mFrame.addView(newBubble);
}
It is part of the code of a course in coursera!
I would recommend you using other options to find the solution (for example by reviewing the videos and the example code gived)
Use following lines in BubbleView Constructor to set the position of your scaled bitmap.
// Adjust position to center the bubble under user's finger
mXPos = x - mScaledBitmapWidth / 2;
mYPos = y - mScaledBitmapWidth / 2;
This will center the bubble view bitmap under user's finger. Coursera's skeleton project already includes these line.
Also, you need one more correction in your code -
#Override
public boolean onTouchEvent(MotionEvent event) {
return this.mGestureDetector.onTouchEvent(event);
//return false;
}
I agree with Ignacio Rubio's suggestion to create the new bubble outside of the for loop however, in this particular scenario where the default value for found is false, the first click will create two bubbles: (1) to satisfy the condition of if(mFrame.getChildCount() == 0) and (2) if(!found)
To resolve this issue you can use an int in place of a boolean
public boolean onSingleTapConfirmed(MotionEvent event) {
int found = 0;
if(mFrame.getChildCount() == 0) {
Log.v(TAG, "Make First Bubble");
BubbleView bubble = new BubbleView(getApplicationContext(),
event.getX(),
event.getY());
mFrame.addView(bubble);
} else {
for(int i=0; i < mFrame.getChildCount(); i++) {
BubbleView bubble = (BubbleView) mFrame.getChildAt(i);
if(bubble.intersects(event.getX(), event.getY())) {
mFrame.removeViewAt(i);
found = 1;
break;
}else{
found = 2;
}
}
}
if (found == 2) {
BubbleView newBubble = new BubbleView(getApplicationContext(),
event.getX(),
event.getY());
mFrame.addView(newBubble);
}
return true;
}
Also, double check you View.intersect(). The touch will have to intersect the x AND y position of the bubble rather than the x OR y position.
i use this code for displays two view mRenderSurfaceView and gestureOverlayView:
relativeLayout = new RelativeLayout(this);
final FrameLayout.LayoutParams relativeLayoutLayoutParams = new FrameLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT , RelativeLayout.LayoutParams.FILL_PARENT );
gestureOverlayView = new GestureOverlayView(this);
gestureOverlayView.setEnabled(true);
this.mRenderSurfaceView = new RenderSurfaceView(this);
this.mRenderSurfaceView.setRenderer(mEngine);
surfaceViewLayoutParams = new RelativeLayout.LayoutParams(super.createSurfaceViewLayoutParams());
gestureOverlayView.addOnGesturePerformedListener(this);
relativeLayout.addView(this.mRenderSurfaceView, surfaceViewLayoutParams);
relativeLayout.addView(gestureOverlayView, 1248,1152);
gestureOverlayView.setVisibility(View.GONE);
this.setContentView(relativeLayout, relativeLayoutLayoutParams);
i drawn with this:
#Override
public boolean onTouch(View v, MotionEvent paramMotionEvent) {
//TODO Auto-generated method stub
if(paramMotionEvent.getAction() == MotionEvent.ACTION_DOWN) {
isDrawing = true;
}
//TouchEvent
if(paramMotionEvent.getAction() == MotionEvent.ACTION_UP) {
isDrawing = false;
rec = new Rectangle[250];
Arrays.fill( rec, null );
irec=0;
}
if (isDrawing = true) {
if (rec[249]!=null){
return false;
}
rec[irec] = new Rectangle(paramMotionEvent.getX(), paramMotionEvent.getY(), 1, 1);
if (irec != 0) {
Line l = new Line(rec[irec-1].getX(), rec[irec-1].getY(), rec[irec].getX(), rec[irec].getY());
l.setColor(0.5f, 1f, 0.3f);
mScene.attachChild(l);
}
irec++;
}
return true;
}
and in OnloadScene i this gestureOverlayView.setOnTouchListener(this);
my Scene do 1248*1152, the problem is public boolean onTouch works fine if i in up left but if move down in scene or right the drawn is shift. how make a view with good Width and height (I guess that is the problem, this keep camera ratio) ?
if i use mScene.setOnSceneTouchListener the drawn is ok but the gesture don't work ...
Solved by velocityOnScreenControl.attachChild(l); of (private AnalogOnScreenControl velocityOnScreenControl;) Use AnalogOnScreenControl example.
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;
}
}
}
Since Android 2.3.1 there is a new feature for ScrollViews and Lists called OverScroll. With
android:overScrollMode="never"
I can turn it off, but if i don't wan't to turn it off how can I change the Color of it?
Afaik there is no way to do this. So I created one; presenting:
Graeme's Amazing Custom List View v2
I've created a custom view which performs overscroll for ListViews and for GridViews (XML example is for slightly more involved GridView but view works for both):
<CustomGlowListView android:id="#+id/searches"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#android:color/black"
android:layout_margin="10dp"
android:tag="GridView"
android:numColumns="3"
android:horizontalSpacing="8dp"
android:verticalSpacing="8dp">
</CustomGlowListView>
The CustomGlowListView looks like this:
public class CustomGlowListView extends RelativeLayout{
private ImageView underscrollEdge;
private ImageView underscrollGlow;
private ImageView overscrollGlow;
private ImageView overscrollEdge;
private AbsListView listView;
private final static float MAX_EDGE_SIZE = 11f;
private final static float MAX_GLOW_SIZE = 93f;
private float scrollDistanceSinceBoundary = 0;
private Rect paddingRectangle = new Rect();
GestureDetector listViewGestureDetector;
// Gives the option of short circuiting the overscroll glow fade (Such as by scrolling away from the overscrolled edge)
boolean interruptFade = false;
public CustomGlowListView(Context context, AttributeSet attrs)
{
super(context, attrs);
listViewGestureDetector = new GestureDetector(new ListViewGestureDetector());
if( getTag() == null ||
getTag().toString().equalsIgnoreCase("ListView")) { listView = new ListView(context); }
else if(getTag().toString().equalsIgnoreCase("GridView"))
{
listView = new GridView(context, attrs);
((GridView)listView).getSelector().getPadding(paddingRectangle);
}
listView.setId(android.R.id.list);
listView.setOverScrollMode(OVER_SCROLL_NEVER);
addView(listView, new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
underscrollEdge = new ImageView(context);
underscrollEdge.setImageResource(R.drawable.underscroll_edge);
underscrollEdge.setScaleType(ScaleType.FIT_XY);
underscrollGlow = new ImageView(context);
underscrollGlow.setImageResource(R.drawable.underscroll_glow);
underscrollGlow.setScaleType(ScaleType.FIT_XY);
overscrollGlow = new ImageView(context);
overscrollGlow.setImageResource(R.drawable.overscroll_glow);
overscrollGlow.setScaleType(ScaleType.FIT_XY);
overscrollEdge = new ImageView(context);
overscrollEdge.setImageResource(R.drawable.overscroll_edge);
overscrollEdge.setScaleType(ScaleType.FIT_XY);
addView(underscrollGlow, getWideLayout(ALIGN_PARENT_TOP));
addView(underscrollEdge, getWideLayout(ALIGN_PARENT_TOP));
addView(overscrollGlow, getWideLayout(ALIGN_PARENT_BOTTOM));
addView(overscrollEdge, getWideLayout(ALIGN_PARENT_BOTTOM));
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(ev.getAction() == MotionEvent.ACTION_DOWN) interruptFade = true;
listViewGestureDetector.onTouchEvent(ev);
if(ev.getAction() == MotionEvent.ACTION_UP) reset();
return super.dispatchTouchEvent(ev);
}
private RelativeLayout.LayoutParams getWideLayout(int alignment)
{
RelativeLayout.LayoutParams returnLayout = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0);
returnLayout.addRule(alignment);
return returnLayout;
}
public void reset()
{
interruptFade = false;
new GlowShrinker().execute(scrollDistanceSinceBoundary);
scrollDistanceSinceBoundary = 0;
}
private class ListViewGestureDetector extends SimpleOnGestureListener
{
#Override
public boolean onScroll(MotionEvent downMotionEvent, MotionEvent currentMotionEvent, float distanceX, float distanceY)
{
float distanceTraveled = downMotionEvent.getY() - currentMotionEvent.getY();
if(listIsAtTop() && distanceTraveled < 0) // At top and finger moving down
{
scrollDistanceSinceBoundary -= distanceY;
scaleEdges(underscrollEdge, underscrollGlow, scrollDistanceSinceBoundary);
}
else if(listIsAtTop() && distanceTraveled > 0 && scrollDistanceSinceBoundary > 0) // At top and finger moving up while in overscroll
{
scrollDistanceSinceBoundary -= distanceY;
scaleEdges(underscrollEdge, underscrollGlow, scrollDistanceSinceBoundary);
}
else if(listIsAtBottom() && distanceTraveled > 0) // At bottom and finger moving up
{
scrollDistanceSinceBoundary += distanceY;
scaleEdges(overscrollEdge, overscrollGlow, scrollDistanceSinceBoundary);
}
else if(listIsAtBottom() && distanceTraveled < 0 && scrollDistanceSinceBoundary > 0) // At bottom and finger moving up while in overscroll
{
scrollDistanceSinceBoundary += distanceY;
scaleEdges(overscrollEdge, overscrollGlow, scrollDistanceSinceBoundary);
}
else if(scrollDistanceSinceBoundary != 0) // Neither over scrolling or under scrolling but was at last check. Reset both graphics.
{
reset();
}
Log.v(CustomGlowListView.class.getSimpleName(), "boundaryDistance = " + scrollDistanceSinceBoundary);
return false;
}
private boolean listIsAtTop() { return listView.getChildAt(0).getTop() - paddingRectangle.top == 0; }
private boolean listIsAtBottom(){ return listView.getChildAt(listView.getChildCount()-1).getBottom() + paddingRectangle.bottom == listView.getHeight(); }
}
private class GlowShrinker extends AsyncTask<Float, Integer, Void>
{
ImageView glow;
ImageView edge;
private final int SHRINK_SPEED = 4;
private final int SHRINK_INCREMENT = 50;
#Override
protected void onPreExecute() {
if(underscrollGlow.getHeight() > 0)
{
glow = underscrollGlow;
edge = underscrollEdge;
}
else if (overscrollGlow.getHeight() > 0)
{
glow = overscrollGlow;
edge = overscrollEdge;
}
else
{
return;
}
}
#Override
protected Void doInBackground(Float... scrollDistanceSinceBoundary) {
if(glow != null && edge != null)
{
int currentSize = (int) scrollDistanceSinceBoundary[0].floatValue();
int shrinkRate = (int) currentSize / SHRINK_INCREMENT;
for(int i=0; i < SHRINK_INCREMENT; i++)
{
if(interruptFade)
{
publishProgress(0);
return null;
}
currentSize -= shrinkRate;
publishProgress(currentSize);
try { Thread.sleep(SHRINK_SPEED); } catch (InterruptedException e) { }
}
}
return null;
}
#Override
protected void onPostExecute(Void result) {
if(glow != null && edge != null)
CustomGlowListView.scaleEdges(edge, glow, 0);
}
#Override
protected void onProgressUpdate(Integer... values) {
CustomGlowListView.scaleEdges(edge, glow, values[0]);
}
}
private static void scaleEdges(ImageView scrollEdge, ImageView scrollGlow, float scrollBy)
{
float edgeSize = scrollBy / 20;
float glowSize = scrollBy / 2;
if(edgeSize > MAX_EDGE_SIZE) edgeSize = MAX_EDGE_SIZE;
if(glowSize > MAX_GLOW_SIZE) glowSize = MAX_GLOW_SIZE;
setHeight(scrollEdge, edgeSize);
setHeight(scrollGlow, glowSize);
}
private static void setHeight(ImageView viewIn, float height)
{
ViewGroup.LayoutParams params = viewIn.getLayoutParams();
params.height = (int) height;
viewIn.setLayoutParams(params);
}
public AbsListView getListView()
{
return listView;
}
}
The overscroll images you can grab from platforms\android-10\data\res\drawable-mdpi\ and then change Hue & Saturation to change color.
I hope this can be a useful start for other ListView customisations - Be interesting to hear of any.
Actually, instead of using a customized ListView, you can simply "hack" your way to changing the color, the glow effect is actually a Drawable embedded in the OS's resources, you can apply a ColorFilter on that:
int glowDrawableId = context.getResources().getIdentifier("overscroll_glow", "drawable", "android");
Drawable androidGlow = context.getResources().getDrawable(glowDrawableId);
androidGlow.setColorFilter(brandColor, PorterDuff.Mode.MULTIPLY);
Read more about it here: http://evendanan.net/android/branding/2013/12/09/branding-edge-effect/
The following method overrides the standard overscroll color including the edge line.
Calling it once in onCreate is enough.
...
ChangeOverScrollGlowColor(getResources(), R.color.red);
...
public static final void ChangeOverScrollGlowColor( Resources res, int colorID ) {
try {
final int glowDrawableId = res.getIdentifier("overscroll_glow", "drawable", "android");
final Drawable overscrollGlow = res.getDrawable(glowDrawableId);
overscrollGlow.setColorFilter(res.getColor(colorID), android.graphics.PorterDuff.Mode.SRC_ATOP);
final int edgeDrawableId = res.getIdentifier("overscroll_edge", "drawable", "android");
final Drawable overscrollEdge = res.getDrawable(edgeDrawableId);
overscrollEdge.setColorFilter(res.getColor(colorID), android.graphics.PorterDuff.Mode.SRC_ATOP);
} catch (Exception e) {
}
}
I've got an android app with a TableLayout with each row having a SeekBar and a ToggleButton. The rows go beyond the visible range of the screen so it's scrollable. When I touch and drag to scroll the page up and in the process touch the SeekBar, it immediately changes the position of the "thumb" instead of scrolling the page. However, the ToggleButton does not behave this way; instead, I can start the drag on the button and scroll then release without the ToggleButton changing state.
Is there anyway to get the SeekBar to behave this way, such that touching it to start a drag will not cause the bar to change positions but instead scroll the page?
I've had similar problems. The only solution I could find (when the seekbar is in a listview) was to disable the seekbar until the item is clicked.
Solution
In the ArrayAdapter I set both enabled and focusable to false and added the SeekBar listener, setting the attributes to false allowed me to use the list item onItemClicked listener. Inside the onItemCLickListener I retreived the seekbar, set the attributes to true, this meant it could be slid up or down. I then disabled it after the adjustment had been made. code below
ArrayAdapter Snippet
this code is inside the creation of the list item, in which the seekbar is housed
seekBar.setClickable(false);
seekBar.setFocusable(false);
seekBar.setEnabled(false);
/* attach listener */
attachProgressUpdatedListener(seekBar, positionOfItemInList);
AttachProgressUpdatedListener
this method attaches the listener to the seekbar, inside the arrayAdapter class
private void attachProgressUpdatedListener(SeekBar seekBar,
final int position) {
seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
public void onStopTrackingTouch(SeekBar seekBar) {
int progress = seekBar.getProgress();
int task_id = (Integer) seekBar.getTag();
TaskHandler taskHandler = new TaskHandler(DBAdapter
.getDBAdapterInstance(getContext()));
taskHandler.updateTaskProgress(task_id, progress);
mList.get(position).setProgress(progress);
//need to fire an update to the activity
notifyDataSetChanged();
seekBar.setEnabled(false);
}
public void onStartTrackingTouch(SeekBar seekBar) {
// empty as onStartTrackingTouch listener not being used in
// current implementation
}
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// empty as onProgressChanged listener not being used in
// current implementation
}
});
}
OnItemCLickListener
this snipped is from the activity which houses the list view.
taskListView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
SeekBar sb = (SeekBar) view.findViewById(R.id.seek);
sb.setFocusable(true);
sb.setEnabled(true);
}
});
I got it working by subclassing the seekbar and checking if the angle of motion is greater than 45 degrees. In that case ignore touches and return false so scroll view will do its thing.
Edit:
Here is an extended SeekBar which allows you to set a min/max in floats rather than integers.
public class SeekBarEx extends SeekBar implements
SeekBar.OnSeekBarChangeListener {
final int SEEK_POINTS = 0x10000;
final String TAG = "SeekBarEx";
public float mMax;
public float mMin;
public OnSeekBarExChangeListener delegate = null;
public interface OnSeekBarExChangeListener {
public void onSeekChanged(SeekBarEx seekBarEx, float value,
boolean fromUser);
public void onStartTrackingTouch(SeekBarEx seekBar);
public void onStopTrackingTouch(SeekBarEx seekBar);
}
public SeekBarEx(Context ctx, AttributeSet attr) {
super(ctx, attr);
super.setMax(SEEK_POINTS);
mMin = 0f;
mMax = 1.0f;
initAttributes(attr);
this.setOnSeekBarChangeListener(this);
}
public void setDelegate(OnSeekBarExChangeListener d) {
delegate = d;
}
public void initAttributes(AttributeSet attrSet) {
TypedArray a;
a = getContext().obtainStyledAttributes(attrSet, R.styleable.SeekBarEx);
final int N = a.getIndexCount();
int i;
for (i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.SeekBarEx_max:
mMax = a.getFloat(i, 1.0f);
Log.d(TAG, "maxSet " + mMax);
break;
case R.styleable.SeekBarEx_min:
mMin = a.getFloat(i, 0f);
Log.d(TAG, "minSet" + mMin);
break;
case R.styleable.SeekBarEx_value:
this.setValue(a.getFloat(i, 0));
break;
}
}
a.recycle();
}
#Override
public int getProgress() {
return super.getProgress();
}
public float getValue() {
float r;
float run;
r = (float) super.getProgress();
r = r / (float) SEEK_POINTS;
run = mMax - mMin;
r = r * run + mMin;
return r;
}
public void setValue(float v) {
if (Float.isNaN(v) || Float.isInfinite(v))
return;
if (v > mMax)
v = mMax;
if (v < mMin)
v = mMin;
float run;
int setv;
run = mMax - mMin;
v -= mMin;
setv = Math.round(v * (float) SEEK_POINTS / run);
super.setProgress(setv);
}
public boolean valueChanged = false;
public void cancelTracking() {
if (oldValue != Float.NaN) {
this.setValue(oldValue);
oldValue = Float.NaN;
valueChanged = false;
acceptTouches = false;
acceptChange = false;
}
}
// we override these methods so that when we forcully cancel
// on ontouches moved. We can revert back to the old value
#Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
Log.d(TAG, "SeekBar changed to " + progress);
if (delegate != null && acceptTouches) {
valueChanged = true;
delegate.onSeekChanged(this, this.getValue(), fromUser);
} else
cancelTracking();
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
if (delegate != null)
delegate.onStartTrackingTouch(this);
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (delegate != null && valueChanged)
delegate.onStopTrackingTouch(this);
else
cancelTracking();
acceptChange = false;
valueChanged = false;
}
public float mY, mX;
public boolean acceptTouches = true;
// acceptChange never read todo: delete
public boolean acceptChange = false;
public float oldValue = Float.NaN;
public ScrollView getScrollView() {
View view;
view = this;
int maxUp;
maxUp = 5;
while (view != null && maxUp > 0) {
view = (View) view.getParent();
ScrollView scroller;
if (view instanceof ScrollView) {
scroller = (ScrollView) view;
return scroller;
}
maxUp--;
}
return null;
}
// **************************************
// This is the important part in achieving the effect in scroll
// view to be nice
#Override
public boolean onTouchEvent(MotionEvent event) {
int action;
action = event.getAction() & MotionEvent.ACTION_MASK;
ScrollView scroller = this.getScrollView();
boolean mayScroll;
mayScroll = true;
if (scroller == null)
mayScroll = false;
else {
int scrollAmount = scroller.getMaxScrollAmount();
if (scrollAmount == 0)
mayScroll = false;
}
switch (action) {
case MotionEvent.ACTION_CANCEL:
Log.d(TAG, "got cancel touches");
cancelTracking();
super.onTouchEvent(event);
return true;
case MotionEvent.ACTION_DOWN:
mX = event.getX();
mY = event.getY();
acceptTouches = true;
acceptChange = false;
oldValue = this.getValue();
valueChanged = false;
break;
case MotionEvent.ACTION_MOVE:
float x;
float y;
x = event.getX();
y = event.getY();
float dx;
float dy;
dx = x - mX;
dy = y - mY;
if (dx < 0)
dx = -dx;
if (dy < 0)
dy = -dy;
y = this.getHeight() / 2 - y;
float angle;
float distance;
distance = dx * dx + dy * dy;
// I just realized this is wrong it should be
// angle = (float)Math.acos(Math.abs(dx)/Math.sqrt(distance))
// I'm leaving it until tested or someone can confirm
angle = (float) Math.atan(dy / dx);
int distanceLimit;
distanceLimit = this.getHeight() / 3;
distanceLimit *= distanceLimit;
// if we move at an angle of atleast 45degrees
// cancel
if (mayScroll && angle > Math.PI / 4.0) {
cancelTracking();
}
mX += 100000;
if (y < 0)
y = -y;
// if we moved finger too far just cancel
// cause the person may have wanted to scroll but
// failed so we revert back to the old value
if (y > this.getHeight() * 2) {
cancelTracking();
} else if (acceptTouches)
acceptChange = true;
break;
default:
break;
}
// if we accept touches do the usual otherwise
// return false so scrollView can do it's thing
if (acceptTouches)
return super.onTouchEvent(event);
return false;
}
}
seekbarex.xml is bellow. This is just to add min/max/value as floats.
<declare-styleable name="SeekBarEx">
<attr name="min" format="float"/>
<attr name="max" format="float"/>
<attr name="value" format="float"/>
</declare-styleable>
</resources>