Android onInterceptTouchEvent, get childs in ViewGroup - android

I have a layout(like board) like this that contains cells(buttons)
| A | B | C | D |
-----------------
| E | F | G | H |
-----------------
| I | J | K | L |
-----------------
| X | Y | Z | W |
I'm adding Cells programmatically, they contains letters I set.
public void addCells(String letters){
removeAllViews();
int let = 0;
for (int j = 0; j < 4; j++){
for (int i = 0; i < 4; i++){
String currentLetter = ""+letters.charAt(let);
//Cell object that contains it's position and letter.
Cell cell_ = new Cell(this.getContext(), new Point(i , j), new Letter(currentLetter));
this.addView(cell_);
this.cells[i][j] = cell_;
let++;
}
}
}
My aim is connecting cells by finger moving like this:
I'm returning true from onTouchEvent() so I can capture all touchs in ViewGroup onInterceptTouchEvent()
public boolean onTouchEvent(MotionEvent motionEvent) {
return true;
}
But I couldn't get the logic. How can I access a certain child object by click/touch in that ViewGroup?
When I click to 'A' letter, I want to access that cell object.

In general:
The parent view intercepts all touch events (x,y)
onInterceptTouchEvent() { return true; }
On touch event, the parent finds the inner view that match the event location
onTouchEvent() { // see below }
Perform action on that view (Light it up, update data, etc)
How the parent can find the inner view?
You can do it in 2 ways,
1) Have a data structure ([][]) for the board that holds a reference to the cells views. Then you know what is the touch event X,Y so if all cells are equal simply calculate the correct cell based on the cells size and location.
View[][] board;
// Add the Views to the board in creation loop
int cellWidth = board[0][0].getMeasuredWidth();
int cellHeight = board[0][0].getMeasuredHeight();
int tragetX = (int)(x / cellWidth);
int targetY = (int)(y / cellHeight);
View targetCell = board[targetX][targetY];
// Do something with targetCell
2) Iterate on all the parent children (last to first is best), and calculate the child view location inside the parent location and with combination of it's size determine if that child is the target or not.
View targetChild = null;
int[] loc = new int[2];
for (int i = getChildCount()-1; i >= 0; i--) {
View child = getChildAt(i);
child.getLocationInWindow(loc);
if (x >= loc[0] && x <= loc[0]+child.getMeasuredWidth() &&
y >= loc[1] && y <= loc[1]+child.getMeasuredHeight()) {
targetChild = child;
break;
}
}
// Do something with targetChild
The above is an example, please write a better code :) (reuse loc, etc...)

Related

Smooth Scroll to Custom View in Android

i am creating custom view extending surfaceview. View is Used to Display Video thumbs horizontally and is used as timeline of video. i want to add smooth scrolling to this custom view. i have tried fling animation class from androidx
https://developer.android.com/guide/topics/graphics/fling-animation
but thisis used to scroll whole view. and using this fling view just graphically view is animating but its value remains the same. alternatively i have tried to use gesture listener but ufotunatilly. not worked can any one guide me in this matter.
what actually i want smooth scrolling
what actually working
public class TimelineView extends SurfaceView
touchmove Evvent method
f2 = ((float) this.timeFullWidth) / ((float) this.viewWidth);
j2 = this.startTouchTime - ((long) (f2 * (f - this.startTouch.x)));
this.currentTime = j2;
if (j2 < mediaList.get(0).getStartTrim()) {
this.currentTime = glitchCamEditorMediaList.get(0).getStartTrim();
}
if (j2 > mediaList.get(0).getEndTrim()) {
this.currentTime = glitchCamEditorMediaList.get(0).getEndTrim();
}
============================================================================
public void onDraw(Canvas canvas) {
int i = 0;
for (Media media: this.mediaList) {
long j = this.currentTime;
int i2 = this.timeFullWidth;
long j2 = j - ((long) (i2 / 2));
if (media.getStartTimeLine() < j + ((long) (i2 / 2)) && media.getEndTrim() > j2) {
drawCard(canvas, media);
}
i++;
}
}

ImageButton Neighboring Validation

I have a 4x4 tile of letters. I wanted to have a validation that only those letters nearest to the tapped/clicked/pressed letter must be clickable and others are set as unclickable. I have my code (see below) for image1. I am having a second thought of how will I enable disabled ImageButton when I tap ImageButton near it.
Example of set:
1 | 5 | 9 | 13
2 | 6 | 10 | 14
3 | 7 | 11 | 15
4 | 8 | 12 | 16
If I clicked/pressed 1 then the 2, 5 and 6 are clickable and the rest are not. Then, I'll click 2 and 1, 3, 5, 6 and 7 must be clickable.
QUESTIONS:
How can I avoid button being clicked again if its already clicked?
How can I enable disabled button and avoiding my Q1?
I am using Android Studio.
Code
if (image1.isPressed()) {
image1.setClickable(false);
image2.setClickable(true);
image3.setClickable(false);
image4.setClickable(false);
image5.setClickable(true);
image6.setClickable(true);
image7.setClickable(false);
image8.setClickable(false);
image9.setClickable(false);
image10.setClickable(false);
image11.setClickable(false);
image12.setClickable(false);
image13.setClickable(false);
image14.setClickable(false);
image15.setClickable(false);
image16.setClickable(false);
}
TIA :)
Avoid writing conditional code. Writing if for each and every image click is a bad practice. So, you can create an arraylist which will always hold the clickable buttons. Based on the clicked button, change this arraylist. Pass this list to a method, which shall enable or disable the clicks for you.
This isn't really about ImageButtons, but rather, what algorithm or methodology would you use to determine which regions are clickable, based on the current state of the system.
We need to separate the logic somewhat. On one side you have your View. Let's just put everything related to updating your grid into a black box, say void updateGrid(List<List< Boolean>> grid). Let's maintain a matrix with entries determining whether or not a button is clickable.
Initially all of the buttons are clickable. You're going to get some notification when a button is clicked, say from a callback void onButtonClick(int i, int j), where i and j give the position of the button. I'm deliberately being vague about this, you'll most likely get an id, and need to resolve the i and j positions yourself. In which case perhaps you'll want to encapsulate the buttonStates and positions in some kind of object - it's up to you.
private <List<List<Boolean>> mGrid;
private static final int GRID_LENGTH = ...;
private static final int GRID_WIDTH = ...;
private static final defaultValue = false;
public void onButtonClick(int i, int j) {
List<List<Boolean>> newGrid = selectGridRegion(i, j);
updateGrid(newGrid);
}
private List<List<Boolean>> selectGridRegion(int i, int j) {
if (!isValidRegion(i, j)) {
return mGrid;
}
ArrayList<ArrayList<Boolean>> grid = makeGrid();
for (int row = 0; row < GRID_LENGTH; row++) {
ArrayList<Boolean> gridRow = grid.get(row);
for (int col = 0; col < GRID_WIDTH; col++) {
gridRow.set(col, isNeighboringRegion(i, j, row, col));
}
}
return grid;
}
private static boolean isNeighboringRegion(int i, int j, int m, int n) {
return (Math.abs(i - m) == 1 && Math.abs(j - n) == 1);
}
private static List<List<Boolean>> makeGrid() {
ArrayList<ArrayList<Boolean>> grid = new ArrayList<ArrayList<Boolean>>();
for (int i = 0; i < GRID_LENGTH; i++) {
ArrayList<Boolean> row = new ArrayList<Boolean>();
for (int j = 0; j < GRID_WIDTH; j++) {
row.add(defaultValue);
}
grid.add(row);
}
return grid;
}
private static boolean isValidRegion(int i, int j) {
boolean isValid = false;
if (0 <= i && i < GRID_LENGTH) {
if (0 <= j && j < GRID_WIDTH) {
isValid = true;
}
}
return isValid;
}
Now, that's a general approach to modeling the grid. All these method except for onButtonClick sound very much like they should be encapsulated in a Grid class! You might want to extend ArrayList, or something similar.

Interchange positions of two Views within GridLayout (Android)

I have programmed a basic gaming App that consists of blocks which can be exchanged by a swipe (similar to CandyCrush). I succeeded in recognizing the swipe gesture but my code that changes the positions of the Views (blocks) within a GridLayout does only work in the emulator but not on a real device (Samsung Galaxy S3).
Also I couldn't find a way of animating the interchange of the two views. I've added android:animateLayoutChanges="true" to the GridLayout XML but it didn't change anything.
Here's my exchanging code:
// change to blocks positions in grid layout
GridLayout.LayoutParams sourceParams = (GridLayout.LayoutParams)this.imageView.getLayoutParams();
GridLayout.LayoutParams targetParams = (GridLayout.LayoutParams)otherBlock.imageView.getLayoutParams();
GridLayout.Spec sourceRowSpec = sourceParams.rowSpec;
GridLayout.Spec sourceColumnSpec = sourceParams.columnSpec;
sourceParams.rowSpec = targetParams.rowSpec;
sourceParams.columnSpec = targetParams.columnSpec;
targetParams.rowSpec = sourceRowSpec;
targetParams.columnSpec = sourceColumnSpec;
this.imageView.setLayoutParams(sourceParams);
otherBlock.imageView.setLayoutParams(targetParams);
gameGridLayout.requestLayout();
And here's a screenshot of the App:
You should have an index table of your layout, and in your drawing method you run through this index to draw each block.
Like that:
private int [][] index;
void onDraw(Canvas canvas) {
for (int i = 0; i < MAX_BLOCK_HEIGHT; i++) {
for (int j = 0; j < MAX_BLOCK_WIDTH; j++) {
// Draw block index[i][j]
}
}
}
void swapBlock(int blockX, int blockY, int relativePos) {
int tmpBlock = index[blockY][blockX];
if (relativePos == AT_LEFT) {
index[blockY][blockX] = index[blockY][blockX - 1];
index[blockY][blockX - 1] = tmpBlock;
}
// And so on...
}
And this index can be helpful all the time, not only for blit.

How to programmatically adjust z order of Views

The below code is my attempt to send mMyView to the front or the back of the set of children of mPivotParent so it will be rendered on top or behind the others. Hiding the view will not suffice in my case.
mPivotParent is a FrameLayout.
Looking at mPivotParent.mChildren shows that my code below "works" in that the ordering is being set correctly. Yet it has no impact on the z order. Not only this, but the framerate gets cumulatively slower and slower the more times the repositioning code gets called. There are 4 children total and mPivotParent.mChildrenCount remains at 4 throughout as expected.
I'm targeting API Level 7.
#Override
public boolean onTouchEvent(MotionEvent event) {
Display display = getWindowManager().getDefaultDisplay();
float x = event.getRawX();
float sWidth = (int)display.getWidth();
float xLerpFromCenter = ((x / sWidth) - .5f) * 2.f; // [-1, 1]
mRotateAnimation.mfLerp = xLerpFromCenter;
if(xLerpFromCenter < -0.2f && mPivotParent.getChildAt(0) != mMyView)
{
mPivotParent.removeView(mMyView);
mPivotParent.addView(mMyView, 0);
refreshEverything();
}
else if(xLerpFromCenter > 0.2f && mPivotParent.getChildAt(0) == mMyView)
{
mPivotParent.removeView(mMyView);
mPivotParent.addView(mMyView, mPivotParent.getChildCount() - 1);
refreshEverything();
}
return super.onTouchEvent(event);
}
private void refreshEverything()
{
for(int i = 0; i < mPivotParent.getChildCount(); ++i)
{
mPivotParent.getChildAt(i).invalidate();
mPivotParent.getChildAt(i).requestLayout();
}
mPivotParent.invalidate();
mPivotParent.requestLayout();
}
Partial Solution
Here's a somewhat inefficient hack but it works for my purpose, which is to take the top item and send it to the back, keeping all other items in their same order.
private void putFrontAtBack()
{
for(int i = 0; i < mPivotParent.getChildCount() - 1; ++i)
{
mPivotParent.getChildAt(0).bringToFront();
}
refreshEverything();
}
Note: This doesn't work in the general case of arbitrary re-ordering.
Try this.
private void reorder(int[] order)
{
if(order == null || order.length != mPivotParent.getChildCount()) return;
for(int i = order.length - 1; i >= 0; i--)
{
mPivotParent.getChildAt(order[i]).bringToFront();
}
refreshEverything();
}
This code provides arbitrary reordering. The integer array "order" is used to indicate the new order of each view, where order[n]=x means the new order of childAt(x) is n.

How can you detect which view you are passing over when performing a touch event?

I want to know how I can detect child views if I move a view from one ViewGroup to another ViewGroup, particularly when doing a touch event. Is there a method I can call that will let me know which views i'm "hovering" over?
What I'm doing right now is when I detect an ACTION_MOVE event on my view i'm raising it to the top level parent so that it can move and be drawn within the entire window ( and not just inside it's original parent bounds ), then I want to move the view across to a different ViewGroup and on ACTION_UP attach the view to that ViewGroup.
Inspired by Ami's response, but discovering that MotionEvent#getX()/getY() along with View#getTop()/etc return coordinates wrt the parent View, I ended up doing the following below to operate in screen coordinates, allowing me to work across ViewGroups:
private boolean inRegion(float x, float y, View v) {
v.getLocationOnScreen(mCoordBuffer);
return mCoordBuffer[0] + v.getWidth() > x && // right edge
mCoordBuffer[1] + v.getHeight() > y && // bottom edge
mCoordBuffer[0] < x && // left edge
mCoordBuffer[1] < y; // top edge
}
whose usage inside an OnTouchListener is e.g.:
boolean inside = inRegion(event.getRawX(), event.getRawY(), targetView);
I think I found a simpler way to do this.
Create an ArrayList of possible targets
Call this method from your touch event, supplying your targets list and the coords
private View findView(float x, float y, ArrayList<View> targets)
{
final int count = targets.size();
for (int i = 0; i < count; i++) {
final View target = targets.get(i);
if (target.getRight() > x && target.getTop() < y
&& target.getBottom() > y && target.getLeft() < x) {
return target;
}
}
return null;
}
I found Sebastian Roth's answer very helpful with resources, but since it wasn't really an answer to my question, I thought I'd share what I came up with.
Here is the code I use to detect views ( only views that will accept a drop that is ) given a coordinate on the screen.
private DropView findDropTarget( int x, int y, int[] dropCoordinates ){
final Rect r = mRectTemp;
final ArrayList<DropView> dropTargets = ((main) context).getBoardDropTargets();
final int count = dropTargets.size();
for (int i=count-1; i>=0; i--) {
final DropView target = dropTargets.get(i);
target.getHitRect(r);
target.getLocationOnScreen(dropCoordinates);
r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop());
if (r.contains(x, y)) {
dropCoordinates[0] = x - dropCoordinates[0];
dropCoordinates[1] = y - dropCoordinates[1];
return target;
}
}
}
Ok, first off mRectTemp is just an allocated Rectangle so you don't have to keep creating new ones ( I.E. final Rect r = new Rect() )
The next line dropTargets is a list of views that will accept a drop in my app.
Next I loop through each view.
I then use getHitRect(r) to return the screen coordiantes of the view.
I then offset the coordiantes to account for the notification bar or any other view that could displace the coordiantes.
finally I see if x and y are inside the coordinates of the given rectangle r ( x and y are the event.rawX() and event.rawY() ).
It actually turned out to be simpler then expected and works very well.
Read this:
http://developer.android.com/reference/android/view/ViewGroup.html#onInterceptTouchEvent(android.view.MotionEvent)
http://developer.android.com/reference/android/view/View.html#onTouchEvent(android.view.MotionEvent)
I had implemented a Drag and Drop using that method.
I also highly recommend a read of the HomeScreen sourcecode, which contains this thing (kind of):
https://android.googlesource.com/platform/packages/apps/Launcher2

Categories

Resources