3
votes

Up till now I have searched for hours on end and have not found any suitable solution to my issue.

Am fiddling around with my android application and trying to make a Sudoku game.

The TableLayout consists of 9 TableRows, each containing 9 TextViews. Thus the 9*9 grid contains 81 TextViews each having a OnTouchListener:

final OnTouchListener cellTouch = new OnTouchListener() {
    public boolean onTouch(View v, MotionEvent e) {
        Log.d("TOUCH", "id: "+v.getId() + " "+e.toString());

        final int action = e.getAction();

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                // set background to different colour
                // set background back if this is different TextView

                break;
            }
            case MotionEvent.ACTION_MOVE: {

                break;
            }               
        }

        return true;
    }       
};

The OnTouch gets fired on each individual TextView, but after touching one and moving the trackball around the following occurs:

  • ACTION_DOWN
  • number of ACTION_MOVE
  • ACTION_UP

However, what I would like is the OnTouch to fire on the other TextViews and make them highlighted. The way I create a cell is shown below:

            TextView cellLabel = (TextView) inflater.inflate(R.layout.sudoku_cell, tr, false);
            cellLabel.setId(curCellId);
            cellLabel.setText(""+ curCellId);

            cellLabel.setOnTouchListener(cellTouch);
            cellLabel.setOnFocusChangeListener(cellFocus);
            cellLabel.setOnClickListener(cellClick);
            cellLabel.setFocusable(true);
            cellLabel.setClickable(true);
            cellLabel.setFocusableInTouchMode(true);

Each individual cell:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="35dp"
android:layout_weight="1"
android:textSize="18dp"
android:textColor="#FFFFFF"
android:gravity="center"
android:background="@layout/border"
/>

I have been trying so many different things. Such as determining the TextView the pointer is on top, but with out any luck.

Hope any of you have an idea. Thank you for your time.

Kind regards.

EDIT

The search function to loop through the TextView[][] and find the appropriate cell. However, only works on a row basis. If I switch from one row to the other it does not work.

private TextView getCell (View v, MotionEvent e) {
    float x = e.getX();
    float y = e.getY();

    for (int i = 0; i < GRID_HEIGHT; i++) {
        for (int j = 0; j < GRID_WIDTH; j++) {
            TextView tv = tvGrid[i][j];

            Rect rectView = new Rect(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());         

            if(rectView.contains((int)x, (int)y)) {
                return tv;
            }
        }
    }

    return null;
}
3
Once a view has said it will handle the touch the other views wont get events until an up action. See Alex's answer below which is pretty much the way I have made this work in the pastIdistic
That is exactly what is happening. Will look into Alex's suggestion.LordOfBones

3 Answers

3
votes

Here's the code I tried myself:

The layout is just a TableLayout with 3 TableRows, and each TableRow contains 3 TextViews with ids like t1, t2, .., t9

And the activity:

public class ImgActivity extends Activity {

protected Map<View, Rect> cells = new HashMap<View, Rect>();

protected boolean hasCoordinatesPopulated;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    final TableLayout table = (TableLayout) findViewById(R.id.table);
    table.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (!hasCoordinatesPopulated) {
                View view = table.findViewById(R.id.t1);
                Rect rect = getRawCoordinatesRect(view);
                cells.put(view, rect);

                view = table.findViewById(R.id.t2);
                rect = getRawCoordinatesRect(view);
                cells.put(view, rect);

                view = table.findViewById(R.id.t3);
                rect = getRawCoordinatesRect(view);
                cells.put(view, rect);

                view = table.findViewById(R.id.t4);
                rect = getRawCoordinatesRect(view);
                cells.put(view, rect);

                view = table.findViewById(R.id.t5);
                rect = getRawCoordinatesRect(view);
                cells.put(view, rect);

                view = table.findViewById(R.id.t6);
                rect = getRawCoordinatesRect(view);
                cells.put(view, rect);

                view = table.findViewById(R.id.t7);
                rect = getRawCoordinatesRect(view);
                cells.put(view, rect);

                view = table.findViewById(R.id.t8);
                rect = getRawCoordinatesRect(view);
                cells.put(view, rect);

                view = table.findViewById(R.id.t9);
                rect = getRawCoordinatesRect(view);
                cells.put(view, rect);

                hasCoordinatesPopulated = true;
            }
        }

        private Rect getRawCoordinatesRect(final View view) {
            int[] coords = new int[2];
            view.getLocationOnScreen(coords);
            Rect rect = new Rect();
            rect.left = coords[0];
            rect.top = coords[1];
            rect.right = rect.left + view.getWidth();
            rect.bottom = rect.top + view.getHeight();
            return rect;
        }
    });
    table.setOnTouchListener(new OnTouchListener() {
        @Override
        public boolean onTouch(final View v, final MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) {
                final int x = (int) event.getRawX();
                final int y = (int) event.getRawY();
                for (final Entry<View, Rect> entry : cells.entrySet()) {
                    final View view = entry.getKey();
                    final Rect rect = entry.getValue();
                    if (rect.contains(x, y)) {
                        view.setBackgroundColor(Color.CYAN);
                    } else {
                        view.setBackgroundColor(Color.TRANSPARENT);
                    }
                }
                return true;
            }
            return false;
        }
    });
}

}

Of course it's a quick sample variant. You can store your last selected view in a separate field (to deselect on move) and when element below the pointer is found, just break the loop saving some time.

0
votes

Have you tried the following:

  1. Get raw coordinates of all your textviews and storing them in a map with key being your TextView (or id) and value being Rect of it's coordinates and space it takes.

  2. Set an ontouchlistener on your tableview, and then, when user moves, or touches something, you can get raw coordinates of the pointer, iterate through your map entries and find TextView currently below

?

P.S. This is first idea that popped in my mind. I'm pretty sure android can provide a more elegant solution.

0
votes

Works like a charm now. Thanks a bunch :>

EDIT: added handleCell()

// Initialization
grid.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        public void onGlobalLayout() {
            if (!populatedCells) {
                handlePopulate();
                populatedCells = true;  
            }
        }
    });

private void handlePopulate () {
    int curCellId = 0;

    for (int i = 0; i < GRID_HEIGHT; i++) {
        for (int j = 0; j < GRID_WIDTH; j++) {
            TextView tv = (TextView) findViewById(curCellId);
            Rect rect = getRawCoordinatesRect(tv);
            Log.d("RECT", "id: "+tv.getId()+" "+rect.toString());

            cells.put(tv, rect);

            curCellId++;
        }
    }
}


// Listener
final OnTouchListener cellTouch = new OnTouchListener() {
    public boolean onTouch(View v, MotionEvent e) {

        final int action = e.getAction();

        switch (action) {
            case MotionEvent.ACTION_DOWN:               
            case MotionEvent.ACTION_MOVE: {
                handleCell(v,e);

                break;
            }               
        }

        return true;
    }       
};

private TextView getCell (View v, MotionEvent e) {
    int x = (int) e.getRawX();
    int y = (int) e.getRawY();

    for (final Entry<View, Rect> entry : cells.entrySet()) {
        final View view = entry.getKey();
        final Rect rect = entry.getValue();

        if(rect.contains(x, y)) {
            return (TextView) view;
        }
    }

    return null;
}

private void handleCell (View v, MotionEvent e) {
    TextView cell = getCell(v, e);

    if (cell != null) {         
        cell.setBackgroundResource(R.layout.border_cell_current);

        curCell = (TextView) cell;

        // do something
    }
}