Android transfer touch event between views - android

Here is my issue.
I have about 7 buttons inside a linear layout and i am trying to "slide" between them highlighting each one as the finger passes over.
So far i have seen that the view that receives the action down event is locked in and receives every following motion event untill action up.
here is what i was trying:
public class LinearRoot extends LinearLayout {
#SuppressWarnings("unused")
private static final String TAG = LinearRoot.class.getSimpleName();
public LinearRoot(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private LinearRoot(Context context) {
super(context);
init();
}
private void init() {
getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
for (int i = 0; i < getChildCount(); i++) {
Rect r = new Rect();
getChildAt(i).getHitRect(r);
Log.e(TAG, r.flattenToString());
map.put(r, getChildAt(i));
}
LinearRoot.this.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
/* (non-Javadoc)
* #see android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent)
*/
View lastView;
private final HashMap<Rect, View> map = new HashMap<Rect, View>();
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return false;
}
Rect rect = new Rect();
if (lastView != null) {
lastView.getGlobalVisibleRect(rect);
if (dispatchTouchToSpecificView(rect, ev)) {
lastView.dispatchTouchEvent(ev);
return false;
} else {
ev.setAction(MotionEvent.ACTION_CANCEL);
lastView.dispatchTouchEvent(ev);
}
}
Iterator<Rect> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
rect = iterator.next();
if (dispatchTouchToSpecificView(rect, ev)) {
lastView = map.get(rect);
map.get(rect).dispatchTouchEvent(ev);
return false;
}
}
return false;
}
private boolean dispatchTouchToSpecificView(Rect r, MotionEvent ev) {
Log.e(TAG, "X: " + (int) ev.getX() + " Y" + (int) ev.getY());
Log.e(TAG, r.flattenToString());
return r.contains((int) ev.getRawX(), (int) ev.getRawY());
}
}
This is the root layout for all the buttons, which delegates the touch events to the appropriate button by getting its global hit rectangle and seeing if the touch event is inside.
Right now this works only partially, and i am not satisfied with the solution, please provide some comments or possibilities to try.
What i am trying to achieve is a layout that has a large number of buttons which are pretty small and i want to highlight the touched ones before the release so the user can adjust his click.

Related

How can I set an onTouchEvent separately on the objects (more than 1) drawn by onDraw() method?

Supposing that I have two circles inside my onDraw() method.
protected void onDraw(Canvas canvas) {
canvas.drawCircle(redX, redY, redRad, redPaint);
canvas.drawCircle(cx, cy, rad, p);
}
nevermind of the variables that I have used, through the method provided by the View class onTouchEvent() I can put an action on a circle if it is alone, but what if I want them to have an on onTouchEvent() separately even though they are at the same onDraw() method, is it possible? What is the best way to do that?
I don't think this is possible as onTouchEvent is method of view class and you can override it only once.
Only thing you can do is to override ontouchEvent and then from its touch position you can find out in which area you are touching and do the operation that you need to do.
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
upCell = -1;
xPos = event.getX();
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
xPos = event.getX();
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
} else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
}
return super.onTouchEvent(event);
}
You need to store the position of each object & check for overlap every time the touch event for this view is invoked.
A sample is given below(Assuming your objects are circles):
public class CircleDrawingView extends View
{
//constructor - to be used when creating view from code
public CircleDrawingView(final Context ct) {
super(ct);
init();
}
//constructor - invoked when view is used in xml
public CircleDrawingView(final Context ct, final AttributeSet attrs) {
super(ct, attrs);
init();
}
//constructor - invoked when view is used in xml
public CircleDrawingView(final Context ct, final AttributeSet attrs, final int defStyle) {
super(ct, attrs, defStyle);
init();
}
Paint mCirclePaint;
ArrayList<CircleArea> allCircles;//list of all circles drawn on the view
private void init() {
allCircles = new ArrayList<>();
//CircleArea is a class defined below. It's constructor params are:
//CircleArea(int CenterX,int CenterY,int Radius,String TAG)
allCircles.add( new CircleArea(150,50,50, "C1"));
allCircles.add( new CircleArea(150,250,90, "C2"));
allCircles.add( new CircleArea(220,550,50, "C3"));
allCircles.add( new CircleArea(250,350,150, "C4"));
mCirclePaint = new Paint();
mCirclePaint.setColor(Color.BLACK);
mCirclePaint.setStyle(Paint.Style.FILL);
}
#Override
public void onDraw(final Canvas canv) {
canv.drawARGB(0, 0, 0, 0);
for (CircleArea obj : allCircles)
canv.drawCircle(obj.centerX, obj.centerY, obj.radius, mCirclePaint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float xTouch = event.getX();
float yTouch = event.getY();
for (CircleArea obj : allCircles)
{
//Check if the touched co-ordinates lies in one of the drawn circle
if(Math.abs(obj.centerX-xTouch)<obj.radius && Math.abs(obj.centerY-yTouch)<obj.radius)
{
onTouch(obj.tag);
//return true; - If you want the touch event of other views to be invoked
//return false; - If you want to consume the touch event,
// i.e. touch event for other views will not be invoked
//Do not return if objects(circles) drawn on this view are overlapping
//and you want to invoke touch event for each object if such a scenario occurs
}
}
return super.onTouchEvent(event);
}
private void onTouch(String tag)
{
switch (tag)
{
case "C1":
//Touched circle 1
break;
case "C2":
//Touched circle 2
break;
case "C3":
//Touched circle 3
break;
case "C4":
//Touched circle 4
break;
default:
//Didnt touch any circle
}
}
/** Stores data about single circle */
private static class CircleArea {
int radius;
int centerX;
int centerY;
String tag;
CircleArea(int centerX, int centerY, int radius, String tag) {
this.radius = radius;
this.centerX = centerX;
this.centerY = centerY;
this.tag = tag;
}
}
}

Gesture onSingleTapConfirmed strange behaviour

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.

HorizontalScrollView smoothScrolTo doesn't work

I have extended the HorizontalScrollView class to implement a certain behavior. Under the LinearLayout inside my CustomHorizontalScrollView I have only 2 child views (lets say ImageView). When the user scrolls more than 50% to one direction, i want my CustomHorizontalScrollView to auto-scroll to the end of the same direction. This is how I implemented it:
CustomHorizontalScrollView class:
public class CustomHorizontalScrollView extends HorizontalScrollView {
private static float downCoordinates = -1;
private static float upCoordinates = -1;
private static int currentPosition = 0;
public CustomHorizontalScrollView(Context ctx) {
super(ctx);
}
public CustomHorizontalScrollView(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN && downCoordinates == -1) {
downCoordinates = ev.getX();
}
else if (ev.getAction() == MotionEvent.ACTION_UP && upCoordinates == -1) {
upCoordinates = ev.getX();
int scrollViewWidth = this.getMeasuredWidth();
double dist = downCoordinates - upCoordinates;
if (Math.abs(dist) > scrollViewWidth / 2) {
//this.setSmoothScrollingEnabled(true);
// Going forwards
if (dist > 0) {
int max = ((LinearLayout)this.getChildAt(0)).getChildAt(1).getMeasuredWidth();
currentPosition = max;
this.scrollTo(max, 0);
}
// Going backwards
else {
currentPosition = 0;
this.scrollTo(0, 0);
}
}
// reseting the saved Coordinates
downCoordinates = -1;
upCoordinates = -1;
}
return super.onTouchEvent(ev);
}
}
Up until here - everything works. The thing is that I want the auto-scrolling to be done smoothly so i tried using the smoothScrollTo function instead of the scrollTo function but then, nothing happens (as in no auto-scrolling). i tried declaring this:
this.setSmoothScrollingEnabled(true);
but also with no success.
Have you tried this?
this.post(new Runnable() {
public void run() {
this.smoothScrollTo(0, this.getBottom());
}
});
This still doesn't work for me, so i find out that i need to after this line
this.smoothScrollTo(0, this.getBottom());
add this
this.invalidate();

Android: drag item out of scrolling list/gallery

I want to implement a Gallery that allows the user to drag items out of it. This shouldn't get in the way of scrolling/flinging.
Given the interface layout, the user can only drag items out of the Gallery in a vertical path, and scroll the Gallery horizontally.
Is this feasible? Is there an easy way of detecting horizontal movements, and defer them to the Gallery's event handlers, and intercept vertical movements? Or do I have to override onInterceptTouchEvent() and do the math myself?
(edit: I'm giving a try to a GestureListener, overriding onFling and onScroll, and passing the events to the Gallery when the vertical scroll distance is below a threshold)
I inherited Gallery, and overrode the onScroll method. I haven't implemented the drop logic yet, but the dragging and scrolling work.
When I can spare the time, I'll write a full post in my blog with more details, and the drop mechanism. For now, a simple copy-paste in case somebody reaches this page in the future.
To keep the behavior where it belongs, I created this DraggableView interface:
public interface DraggableView {
public void beforeDrag();
public DragView createDragView();
public Object getDraggedInfo();
public void afterDrop();
}
Views in the Gallery can be dragged out of the Gallery area if they implement this view. They are notified before and after, and must implement two methods:
createDragView() returns a DragView object. Basically, a transparent hovering bitmap to accompany the user's movement.
getDraggedInfo() returns the information that should reach the drop target.
Here's the DragView class:
public class DragView extends ImageView {
private final LayoutParams mLayoutParams;
public DragView(Context context, Bitmap bitmap) {
super(context);
mLayoutParams = new LayoutParams();
mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
mLayoutParams.height = LayoutParams.WRAP_CONTENT;
mLayoutParams.width = LayoutParams.WRAP_CONTENT;
mLayoutParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_NOT_TOUCHABLE;
mLayoutParams.format = PixelFormat.TRANSLUCENT;
mLayoutParams.windowAnimations = 0;
mLayoutParams.alpha = 0.5f;
setImageBitmap(bitmap);
setLayoutParams(mLayoutParams);
}
public void move(int x, int y) {
mLayoutParams.x = x;
mLayoutParams.y = y;
}
}
As you can see, it takes a Bitmap in construction, and creates a hovering ImageView. Finally, here is the (just implemented and not very clean) Gallery code to make it all happen:
public class DraggableItemGallery extends Gallery {
private boolean mDragging;
private DragView mDragView;
private DraggableView mDragViewOwner;
private WindowManager mWindowManager;
private boolean mScrollStarted;
public DraggableItemGallery(Context context) {
super(context);
initialize();
}
public DraggableItemGallery(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public DraggableItemGallery(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
public void initialize() {
mWindowManager = (WindowManager)
getContext().getSystemService("window");
}
private void startDraggingItem(DraggableView view, int x, int y) {
mDragging = true;
mDragViewOwner = view;
mDragView = view.createDragView();
mDragView.move(x, y);
mWindowManager.addView(mDragView, mDragView.getLayoutParams());
}
private void continueDraggingItem(int x, int y) {
DragView dragView = getDragView();
dragView.move(x, y);
mWindowManager.updateViewLayout(dragView, dragView.getLayoutParams());
}
private void stopDraggingItem() {
mDragging = false;
mWindowManager.removeView(mDragView);
mDragViewOwner.afterDrop();
mDragView = null;
mDragViewOwner = null;
}
private DraggableView getDraggedItem() {
return mDragViewOwner;
}
private DragView getDragView() {
return mDragView;
}
private boolean isDraggingItem() {
return (mDragging);
}
private void setScrolling(boolean scrolling) {
mScrollStarted = scrolling;
System.out.println("Scrolling " + scrolling);
}
private boolean isScrolling() {
return mScrollStarted;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if ((event.getAction() & ACTION_MASK) == ACTION_UP) {
setScrolling(false);
if (isDraggingItem())
stopDraggingItem();
}
return super.onTouchEvent(event);
}
final Rect onScroll_tempRect = new Rect();
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (isScrolling()) {
if (isDraggingItem()) {
int x = (int) e2.getX(),
y = (int) e2.getY();
System.out.println("Moving to " + x + " " + y);
continueDraggingItem(x, y);
return true;
} else {
/* Not dragging, let the Gallery handle the event */
return super.onScroll(e1, e2, distanceX, distanceY);
}
} else {
setScrolling(true);
boolean isVertical = (Math.abs(distanceY) > Math.abs(distanceX));
if (isVertical) {
int x = (int) e1.getX(),
y = (int) e1.getY();
View hitChild = null;
// A tiny optimization, declared above this method
final Rect hitRect = onScroll_tempRect;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.getHitRect(hitRect);
if (hitRect.contains(x, y)) {
hitChild = child;
break;
}
}
if (hitChild instanceof DraggableView) {
startDraggingItem((DraggableView) hitChild, x, y);
return true;
}
}
/* Either the scroll is not vertical, or the point
* of origin is not above a DraggableView. Again,
* we let the Gallery handle the event.
*/
return super.onScroll(e1, e2, distanceX, distanceY);
}
}
}
Hope it helps.
Here is something I did to do exactly that. That's only the code for the activity... there is some layout and other res files you'll need...
Every list item has an icon and name matched randomly.
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.widget.FrameLayout.LayoutParams;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import java.util.ArrayList;
import java.util.Arrays;
public class DragActivity extends Activity implements View.OnTouchListener, AdapterView.OnItemLongClickListener
{
private static final String TAG="DragActivity";
private static final int NOT_DRAGGING = 0;
private static final int DRAGGING = 1;
private int state=NOT_DRAGGING;
private ImageView draggable =null;
private int dragged_position;
float current_x, current_y;
int current_icon = R.drawable.notepad;
private ArrayList<String> names = new ArrayList<String>(Arrays.asList("John", "Mark", "Mathew", "Luke", "Bob", "Will", "Brian", "Mike"));
private ArrayList<Integer> icons = new ArrayList<Integer>(Arrays.asList( R.drawable.glasses, R.drawable.monkey, R.drawable.normal, R.drawable.smile, R.drawable.wink));
private ArrayList<Integer> matching;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setupListContent();
ListView list = (ListView) findViewById(R.id.main_list);
list.setAdapter(new DragListAdapter());
list.setOnItemLongClickListener(this);
list.setOnTouchListener(this);
// need to use the same view for the both listeners, as described in Android documentation :
// http://developer.android.com/guide/topics/ui/ui-events.html
// onTouch() - This returns a boolean to indicate whether your listener consumes this event. The important thing
// is that this event can have multiple actions that follow each other. So, if you return false when the down action
// event is received, you indicate that you have not consumed the event and are also not interested in subsequent
// actions from this event. Thus, you will not be called for any other actions within the event, such as a finger
// gesture, or the eventual up action event.
ImageView image = (ImageView) findViewById(R.id.main_image);
image.setImageResource(current_icon);
}
private void setupListContent() {
matching = new ArrayList<Integer>();
for (int i=0; i<names.size(); i++) {
matching.add((int) (icons.size() * Math.random()));
}
}
#SuppressWarnings("unchecked")
private class DragListAdapter extends ArrayAdapter {
public DragListAdapter() {
super(DragActivity.this, R.layout.list_item, names);
}
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if (row == null) {
LayoutInflater inflater = getLayoutInflater();
row = inflater.inflate(R.layout.list_item, parent, false);
}
row.setDrawingCacheEnabled(true);
TextView name = (TextView) row.findViewById(R.id.item_text);
ImageView icon = (ImageView) row.findViewById(R.id.item_icon);
name.setText(names.get(position));
icon.setImageResource(icons.get(matching.get(position)));
return row;
}
}
private boolean checkOnDropIcon(MotionEvent me) {
ImageView drop_icon = (ImageView) findViewById(R.id.main_image);
Rect icon_rect = new Rect();
drop_icon.getGlobalVisibleRect(icon_rect);
Log.d(TAG, "icon at " + icon_rect.left + "<- ->" + icon_rect.right + ", " +
icon_rect.top + " ^ v" + icon_rect.bottom);
if ((me.getRawX()<icon_rect.left) || (me.getRawX()>icon_rect.right) ||
(me.getRawY()<icon_rect.top) || (me.getRawY()>icon_rect.bottom)) {
return false;
}
else {
return true;
}
}
private void checkOnDrop(MotionEvent me) {
boolean onDropIcon = checkOnDropIcon(me);
ImageView image = (ImageView) findViewById(R.id.main_image);
if ((onDropIcon) && (current_icon==R.drawable.notepad)) {
current_icon = R.drawable.exit;
image.setImageResource(current_icon);
image.invalidate();
return;
}
if ((!onDropIcon) && (current_icon==R.drawable.exit)) {
current_icon = R.drawable.notepad;
image.setImageResource(current_icon);
image.invalidate();
return;
}
}
public boolean onTouch(View view, MotionEvent me) {
if (state == NOT_DRAGGING) {
// get the position of the touch so we know where to place the dragging item if it is a long press
current_x = me.getRawX();
current_y = me.getRawY();
return false;
}
else {
FrameLayout frame = (FrameLayout) findViewById(R.id.drag_space);
if (me.getAction()==MotionEvent.ACTION_UP) {
frame.removeAllViews();
draggable=null;
frame.setVisibility(View.GONE);
state=NOT_DRAGGING;
// check if we dropped a name
if (checkOnDropIcon(me)) {
names.remove(dragged_position);
matching.remove(dragged_position);
ListView list = (ListView) findViewById(R.id.main_list);
DragListAdapter adapter = (DragListAdapter) list.getAdapter();
adapter.notifyDataSetChanged();
}
// restore the icon
ImageView image = (ImageView) findViewById(R.id.main_image);
current_icon = R.drawable.notepad;
image.setImageResource(current_icon);
image.invalidate();
}
if (me.getAction()==MotionEvent.ACTION_MOVE) {
int frame_position[] = new int[2];
frame.getLocationOnScreen(frame_position);
draggable.setPadding(
(int) me.getRawX()-frame_position[0]-(draggable.getDrawable().getIntrinsicWidth()/2),
(int) me.getRawY()-frame_position[1]-(draggable.getDrawable().getIntrinsicHeight()/2),
0, 0);
draggable.invalidate();
checkOnDrop(me);
}
return true;
}
}
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
if (state == DRAGGING) {
Log.d(TAG, "already have an object moving... ?");
return false;
}
FrameLayout frame = (FrameLayout) findViewById(R.id.drag_space);
int frame_position[] = new int[2];
frame.getLocationOnScreen(frame_position);
// setup everything for dragging
state = DRAGGING;
dragged_position = i;
draggable = new ImageView(this);
Bitmap bm = view.getDrawingCache();
draggable.setImageBitmap(bm);
draggable.setAlpha(150);
draggable.setScaleType(ImageView.ScaleType.CENTER);
draggable.setDrawingCacheEnabled(true);
draggable.setPadding((int) current_x-frame_position[0]-(bm.getWidth()/2), (int) current_y-frame_position[1]-(bm.getHeight()/2), 0, 0);
frame.setVisibility(View.VISIBLE);
frame.addView(draggable, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
return true;
}
}

Making a relative layout's content larger than the screen

I am making a grid-based game that will be much larger than the screen, and the user would scroll around in it. I basically put a bunch on ImageViews inside of a custom class that extends a relative layout. The problem is that even though RelativeLayout.LayoutParams is set to the correct size I want (1280*1280). The images are crammed against the sides of the screen and don't extend past it. I have got the scrolling logic working, and when I scroll, I can see it is a rectangle of images the size of one screen. How can I make it so the images extend past the screen?
The class that extends a relative layout:
public class GameGrid extends RelativeLayout {
ImageView[][] subViews;
int rows=0, cols=0, cellsize=0;
int width, height;
//Dragging variables
float startX;
float startY;
float lastX;
float lastY;
boolean touching;
boolean dragging;
int clickedChild;
public GameGrid(Context context) {
super(context);
init();
}
public GameGrid(Context context, int rws, int cls, int clsze) {
super(context);
rows=rws;
cols=cls;
cellsize=clsze;
init();
}
public GameGrid(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GameGrid(Context context, AttributeSet attrs, int defaultStyles) {
super(context, attrs, defaultStyles);
init();
}
public void init() {
rows=10;
cols=10;
cellsize=128;
startX = 0;
startY = 0;
lastX=0;
lastY=0;
touching = false;
dragging = false;
clickedChild = -1;
subViews = new ImageView[cols][rows];
setLayoutParams(new RelativeLayout.LayoutParams(cellsize*cols,cellsize*rows));
width=this.getLayoutParams().width;
height=this.getLayoutParams().height;
this.setMinimumWidth(width);
this.setMinimumHeight(height);
Log.i("info","****************");
Log.i("info","GameGrid Made.");
Log.i("info","width: "+width+"\nheight: "+height);
Log.i("info","****************");
makeGrid();
// this.setOnTouchListener()
}
public boolean getDragging(){
return dragging;
}
public void makeGrid() {
for(int y=0;y<rows;y++){
for(int x=0;x<cols;x++){
ImageView temp = new ImageView(getContext());
temp.setImageResource(R.drawable.water1);
temp.setScaleType(ImageView.ScaleType.FIT_XY);
RelativeLayout.LayoutParams temp2 = new RelativeLayout.LayoutParams(width/cols,height/rows);
if (x == 0 && y == 0){ //If this is the first view being made, set it relative to the parent.
temp2.addRule(RelativeLayout.ALIGN_PARENT_TOP);
temp2.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
}
else if (x == 0){ //If this is in the first column, set it below the one above.
temp2.addRule(RelativeLayout.ALIGN_LEFT,subViews[0][y-1].getId());
temp2.addRule(RelativeLayout.BELOW,subViews[0][y-1].getId());
}
else { //Align the bottom with first one of that row.
temp2.addRule(RelativeLayout.RIGHT_OF,subViews[x-1][y].getId());
temp2.addRule(RelativeLayout.ALIGN_BOTTOM,subViews[0][y].getId());
}
temp.setLayoutParams(temp2);
subViews[x][y]=temp;
subViews[x][y].setId(x+y*cols+1);
// Toast.makeText(getContext(), "" + v.getId(), Toast.LENGTH_SHORT).show();
subViews[x][y].setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v,MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN)
clickedChild = v.getId();
return false;
}
});
addView(temp);
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN)
{ // when the user touches the screen
startX = event.getX();
startY = event.getY();
lastX = event.getX();
lastY = event.getY();
touching = true;
dragging = false;
return true;
}
else if (event.getAction() == MotionEvent.ACTION_MOVE)
{ // when the user moves the touch
if (!dragging)
dragging = true;
int distX = (int)(event.getX()-lastX);
int distY = (int)(event.getY()-lastY);
this.scrollBy(-distX, -distY);
lastX = event.getX();
lastY = event.getY();
return true;
}
else if (event.getAction() == MotionEvent.ACTION_UP)
{ // when the user lifts the touch
if (!dragging){
if (clickedChild>0){
Toast.makeText(getContext(), "getHeight()= " + getHeight(), Toast.LENGTH_SHORT).show();
clickedChild = -1;
}
}
touching = false;
dragging = false;
return true;
}
else if (event.getAction() == MotionEvent.ACTION_CANCEL)
{ // if something gets lost in translation
startX = 0;
startY = 0;
lastX=0;
lastY=0;
touching = false;
dragging = false;
return true;
}
return false;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth, parentHeight);
}
The Activity:
public class Attacktics2 extends Activity {
/** Called when the activity is first created. */
GameGrid grid;
int rows, cols, cellsize;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
}
public void start(View view) {
view.setVisibility(View.GONE);
grid = new GameGrid(this,10,10,128);
setContentView(grid);
}
}
Since you're already doing the heavy lifting of managing all the scrolling, I'd suggest that you implement your entire layout logic yourself and not rely on RelativeLayout. Except for ScrollView and HorizontalScrollView, the stock layout classes are going to restrict their children to be within the parent bounds. Those, in turn, will be restricted to the screen dimensions. If you handle the layout logic yourself, you can position child views so that they extend off screen. It then forms a viewport into a larger grid and can just render those children that are visible within the viewport.

Categories

Resources