Currently I'm using ListView and it's working fine. But I have text in a ListView that is like a paragraph and I just want to show those 2 lines of text and make the rest of the text scrollable, but I'm having an issue that if I make the TextView scrollable inside of the ListView, then the TextView get the focus of its parent (ListView) and won't let it be scrolled.
So can I achieve this scrollable TextView functionality that won't disturb the scrolling property of the ListView?
Thank you.
I was able to do this in the following way:
Into the getView method of the ListAdapter obtain the TextView object of the line, and write
textView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
v.getParent().getParent().requestDisallowInterceptTouchEvent(true);
return false ;
}
});
when you will touch the TextView, take control of the scrolling
What you are trying to achieve really is impossible. How can the OS know that you are trying to scroll the list vs the list row paragraph? You would essentially need to scroll to the bottom of the list row paragraph before the actual list itself could scroll. This is confusing to the user, and not common UX.
I would suggest you look into ExpandableListView. It allows you to have collapsed versions of each row, in your case just 2 lines of text for each list row. When the user taps on the row, it could expand to the full paragraph form, and the list would be scrollable the whole time. There are plenty of tutorials you should be able to find online.
Nothing is Impossible yet way to do things are may b difficult. Directly this thing can`t be achieved but indirectly yes it can be achieved, and yes i achieved.
how did i achieve is a bit complex but yes will share that how did i achieve.
In a ListView when i click on Textview i block the Touch mode of the listView so that their toch method don't intercept each other, and that can be done by using requestDisallowInterceptTouchEvent(true);
this block the TouchListener of the parent (ListView).
Now when click on TextView i allow its touch listener and also setMovementMethod()
but for Movement i made a custom class and Class is Following
public class myScrollMethod extends ScrollingMovementMethod {
#Override
public void onTakeFocus(TextView widget, Spannable text, int dir) {
// TODO Auto-generated method stub
super.onTakeFocus(widget, text, dir);
}
#Override
public boolean onKeyDown(TextView widget, Spannable buffer,
int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_DOWN:
for (int i = 0, scrollAmount = getScrollAmount(widget); i < scrollAmount; i++) {
down(widget, buffer);
}
return true;
case KeyEvent.KEYCODE_DPAD_UP:
for (int i = 0, scrollAmount = getScrollAmount(widget); i < scrollAmount; i++) {
up(widget, buffer);
}
return true;
default:
return super.onKeyDown(widget, buffer, keyCode, event);
}
}
private int getScrollAmount(TextView widget) {
final int visibleLineCount = (int) ((1f * widget.getHeight()) / widget
.getLineHeight());
int scrollAmount = visibleLineCount - 1;
if (scrollAmount < 1) {
scrollAmount = 1;
}
return scrollAmount;
}
}
After that when i click on parent i enable the TouchIntercepter of the parent set true and that get hold on its parent and start scrolling.
By this way i have successfully achieved this requirement
Related
Really not sure if I understand this or maybe going about it wrong. I have a gridlayout (cells are textview) wrapped inside a horizontal scroll which is inside a vertical scroll. I am using the Dpad to navigate across the grid. This works well, as I press the right arrow pad the grid cells move left to right as expected and right to left as left arrow pad is pressed. I have added an onKeylistener attached to each textView of the grid, as I scroll across the grid I am changing the color back ground. The problem is that the onKeyListener apparently takes over the control of the grid. The scroll right works for changing the color but the cells no longer move on the grid. Once I get to last visible column focus continues off screen but the cells stay off screen. Is there a way to implement the scroll inside the onkey event so the cells shift and I have control over the properties of the cell? Or is there a totally different way of doing making this work?
The main components are
textViewD.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View view, int i, KeyEvent keyEvent) {
if(keyEvent.getAction()==KeyEvent.ACTION_DOWN && i == KeyEvent.KEYCODE_DPAD_RIGHT) {
gridLayoutE.getChildAt(childIndex[0]).setBackgroundColor(Color.BLACK);
gridLayoutE.getChildAt(childIndex[0] + 1).setBackgroundColor(Color.GREEN);
childIndex[0] = childIndex[0] + 1;
gridLayoutE.requestFocus();
return true;
}
return false;
}
The scrolllistener, I have a class that extends the horizontalscroll in order to have my header table scroll along with my grid. This works.
#Override
public void onScrollChanged (ObservableScrollView scrollView,int x, int y, int oldx, int oldy){
if (scrollView == hsvHeader) {
hsvBody.scrollTo(x, y);
} else if (scrollView == hsvBody) {
hsvHeader.scrollTo(x, y);
}
}
I was able to find a solution to my problem by using dispatchKeyEvent(KeyEvent event). I ended up with something like
public boolean dispatchKeyEvent(KeyEvent event) {
mcols= mcols + 1;
if(event.getAction()==KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
gridLayoutE.getFocusedChild().setBackgroundColor(Color.BLACK);
gridLayoutE.findViewById(mcol).setBackgroundColor(Color.GREEN);
}
return super.dispatchKeyEvent(event);
}
This along with my scrolllistener, allowed my to scroll the grid and change each cell as I did.
I have been working on the launcher app for android similar to nova launcher. I have setup OnItemLongClickListener and OnDragListener. When i long click on an icon a popup is displayed with menu like "Remove", "Change Icon" etc. Following figure shows the progress of the app with popup open while Long click.
The problem is when the popup is opened the drag works but drop doesnot work. It seems that the i cannot log the x, y position once the popup is open. Also when the drop is performed the following message is shown in logcat.
I/ViewRootImpl: Reporting drop result: false
My code goes something like this in OnDragListener
public boolean onDrag(View v, DragEvent event) {
int dragEvent = event.getAction();
switch (dragEvent)
{
case DragEvent.ACTION_DRAG_LOCATION:
//Open popup here; note: its opened only once. popup.show();
//Log.i("Position x : ", Float.toString(event.getX())); log x or y
/*code to detect x any y change amount and close the popup
once user drags the icon little further and app knows that
user is trying to drag instead of opening the popup
and hence close the popup. popup.dismiss();
*/
// other case like ACTION_DROP etx goes after this
}
}
But it seems that after the popup is opened i cannot log x or y; also the code that determines if the action was intended for "drag" or "popup open", cannot be run.
So how do i solve this problem? I want to close the popup once the drag amount in any is sufficient to know that user wants to drag. And if not stop the drag and display the popup only.
Edit
I solved the problem with popup by using both OnTouchListner and OnDragListner. Following shows my code for OnDragListner.
//bottomAppDrawer is a GridView
bottomAppDrawer.setOnDragListener(new View.OnDragListener() {
#Override
public boolean onDrag(View v, DragEvent event) {
int dragEvent = event.getAction();
LinearLayout draggedItem = (LinearLayout) event.getLocalState(); //dragged LinearLayout
GridView targetItem = (GridView) v; /* How do i get this drop target as LinearLayout so that i can delete or swap data */
switch (dragEvent)
{
case DragEvent.ACTION_DRAG_LOCATION:
if(reset==false) {
dragPositionStart = event.getX();
reset= true;
}
if(Math.abs(dragPositionStart - event.getX())>=20) {
Log.i("Position close : ", Float.toString(dragPositionStart));
if(isPopupOpen) {
popupMenu.dismiss();
v.startDrag(data, dragShadow, itemView, 0);
Toast.makeText(mContext, "popup closed", Toast.LENGTH_SHORT).show();
isPopupOpen = false;
}
reset = false;
}
break;
case DragEvent.ACTION_DROP:
Toast.makeText(mContext, "drop" + Integer.toString(targetItem.getChildCount()), Toast.LENGTH_SHORT).show();
break;
}
return true;
}
});
Now the problem is I am getting the drop target "Gridview" as I am dropping LinearLayout in "Gridview". Also this "LinearLayout is child of the "Gridview". And i want the drop target to be another "LinearLayout" inside the same "GridView". So that i can swap data or reorder. As in figure below.
From what I understand there are two things that you want to do. 1)Reorder the Views after drag. and 2)Change the View types after the reorder.
For problem 1 since it is a grid view it sounds like we really just want to reorder the data in the adapter, and possibly change the data to cause it to display differently. But we need to figure out the position of the original item and the target destination position.
we can extend the GridView to do that:
public class DragAndDropGridView extends GridView {
public void handleMove(int x, int y, int originalPosition) {
Rect rect = new Rect();
PushbackAdapter adapter = (PushbackAdapter) getAdapter();
for (int visiblePosition = getFirstVisiblePosition(); visiblePosition <= getLastVisiblePosition(); visiblePosition++) {
// TODO verify that there are no edge cases not covered by this
View view = getChildAt(visiblePosition);
int left = view.getLeft();
int top = view.getTop();
getChildVisibleRect(view, rect, null);
rect.offsetTo(left, top);
if (rect.contains(x, y)) {
// yay the user tried drop the view at this location
// determine if they wanted to drop it here or after this side
int centerX = rect.centerX();
if (x <= centerX) {
// we want to drop it here
adapter.move(originalPosition, visiblePosition);
adapter.notifyDataSetInvalidated();
break;
} else {
// we want to drop it behind
adapter.move(originalPosition, visiblePosition + 1);
adapter.notifyDataSetInvalidated();
break;
}
}
}
}
}
That leaves us with calling the handleMoveMethod. We do that from within the ACTION_DROP method.
case DragEvent.ACTION_DROP:
handleMove((int)event.getX(), (int)event.getY(), getPositionForView(draggedItem));
Toast.makeText(mContext, "drop" + Integer.toString(targetItem.getChildCount()), Toast.LENGTH_SHORT).show();
break;
Lastly(problem 2) it sounds like you may want to change either the content of the object at the position or the type of view it is contained in. I'd suggest using the getItemViewType and getItemViewTypeCount methods if you need to have different types of views. E.g. something along the lines of the following:
private static class PushbackAdapter extends ArrayAdapter {
ArrayList<Object> mItems;
public void move(int originalPosition, int targetPosition){
// TODO verify that this move logic is correct
Object item = mItems.remove(originalPosition);
item.useLinearLayoutType(true);
mItems.add(targetPosition, item);
}
...
#Override
public int getItemViewType(int i) {
return mItems.get(i).isLeanearLayoutType()? 1 : 0;
}
There could be bugs with this so please test thoroughly
Find position of LinerLayout(which dragging) in Gridview using targetItem.pointToPosition(..).
Swipe LinerLayout using below code:
int i =targetItem.pointToPosition((int)event.getX(), (int)event.getY());
int j = Integer.parseInt(event.getClipData().getItemAt(0).getText().toString());
Collections.swap(targetItem, i, j);//swap Linerlayout
Log.i(TAG, "Swapped " + i+ " with " + j);
Code is not tested. I hope its help you. :)
I have a RecyclerView (with LinearLayoutManager) and a custom RecyclerView.ItemDecoration for it.
Let's say, I want to have buttons in the decoration view (for some reason..).
I inflate the layout with button, it draws properly. But I can't make the button clickable. If I press on it, nothing happening(it stays the same, no pressing effect) and onClick event is not firing.
The structure of ItemDecoration layout is
<LinearLayout>
<TextView/>
<Button/>
</LinearLayout>
And I'm trying to set listener in ViewHolder of the decoration
class ItemDecorationHolder extends RecyclerView.ViewHolder {
public TextView header;
public Button button;
public HeaderHolder(View itemView) {
super(itemView);
header = (TextView)itemView.findViewById(R.id.header);
button = (Button)itemView.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//.. Show toast, etc.
}
});
}
}
And i'm drawing the decoration in onDrawOver method. (actually, I'm modifying this codebase: https://github.com/edubarr/header-decor )
Any ideas? Is it doable?
Thanks!
While the real header is scroll off the screen, the visible one is drawing on canvas directly ,not like a normal interactive widget.
You have these options
Override RecyclerView.onInterceptTouchEvent(), though with some invasiveness so I prefer the next one.
Make use of RecyclerView.addOnItemTouchListener(), remember the motion event argument has been translated into RecyclerView's coordinate system.
Use a real header view, but that will go a little far I think.
If you take option 1/2, Button.setPressed(true) and redraw the header will have a visual press effect.
In addition to what Neil said,
the answer here might help.
Passing MotionEvents from RecyclerView.OnItemTouchListener to GestureDetectorCompat
And then you just need to calculate the height of the header and see if the click falls onto that header view and handle the event yourself.
private class RecyclerViewOnGestureListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
float touchY = e.getY();
ALLog.i(this, "Recyclerview single tap confirmed y: " + touchY);
//mGroupHeaderHeight is the height of the header which is used to determine whether the click is inside of the view, hopefully it's a fixed size it would make things easier here
if(touchY < mGroupHeaderHeight) {
int itemAdapterPosition = mRecyclerView.getLayoutManager().getPosition(mRecyclerView.findChildViewUnder(0, mGroupHeaderHeight));
//Do stuff here no you have the position of the item that's been clicked
return true;
}
return super.onSingleTapConfirmed(e);
}
#Override
public boolean onDown(MotionEvent e) {
float touchY = e.getY();
if(touchY < mGroupHeaderHeight) {
return true;
}
return super.onDown(e);
}
#Override
public boolean onSingleTapUp(MotionEvent e) {
float touchY = e.getY();
if(touchY < mGroupHeaderHeight) {
return true;
}
return super.onSingleTapUp(e);
}
}
As Neil is pointing out, things are getting more complicated than that. However by definition you can't.
So, why not including good libraries that do that and more?
I propose my hard work for clickable sticky header in my FlexibleAdapter project, which uses a real view (not decorators) to handle click events on headers when sticky.
There's also a working demo and a Wiki page on that part (and not only).
I have come up with an idea such that on the touch of the two TextViews both become invisible. This idea works when I touch a single finger on the TextView and only one TextView becomes invisible. But when I test it with two finger, only one Textview becomes invisible. It does not make both the textviews invisble.
I have written the following code.
public class MatchMeaning1 extends Activity implements OnTouchListener{
private static final String TAG = MatchMeaning1.class.getSimpleName();
TextView[] txtWord, txtMeaning;
int [] wordID = {R.id.txtWord1, R.id.txtWord2, R.id.txtWord3, R.id.txtWord4, R.id.txtWord5};
int[] meaningID = {R.id.txtMeaning1,R.id.txtMeaning2, R.id.txtMeaning3, R.id.txtMeaning4, R.id.txtMeaning5 };
String[] word = {"1.abidcation","2.abhor","3.abide","4.abyssmal","5.award"};
String[] meaning = {"a.deep","b.stay","c.cede","d.accolade","5.hate"};
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.match_meaning);
txtWord = new TextView[5];
txtMeaning = new TextView[5];
for(int i = 0; i < txtWord.length; i++){
txtWord[i] = (TextView)findViewById(wordID[i]);
txtMeaning[i] = (TextView)findViewById(meaningID[i]);
txtWord[i].setText(word[i]);
txtMeaning[i].setText(meaning[i]);
txtWord[i].setOnTouchListener(this);
txtMeaning[i].setOnTouchListener(this);
}
}
#Override
public boolean onTouch(View v, MotionEvent event) {
int pointerCount = event.getPointerCount();
if(pointerCount == 2){
TextView [] tv = new TextView[2];
for(int i = 0; i < tv.length ;i++){
tv[i] = (TextView)v;
tv[i].setVisibility(TextView.INVISIBLE);
}
}
return true;
}
}
The above code doesn't work for pointerCount = 2, but it works for pointerCount = 1.
Can anyone tell me the solution for this?
The onTouch event is processed on a per view basis so it would be firing for both textviews except you're returning true which swallows the event. What you want is probably closer to:
#Override
public boolean onTouch(View v, MotionEvent event) {
if(v != YOUR_TEXT_VIEW1 && v != YOUR_TEXT_VIEW2) return false;
((TextView)v).setVisibility(TextView.INVISIBLE);
return false;
}
Replace YOUR_TEXT_VIEW 1 & 2 with objects you declare as the current two text views you want to disappear.
EDIT:
If you only want the views to disappear if the user is touching both of them you're going to have to devise a more involved approach. I see two possibilities:
The onTouch event needs to call another function which appends all views triggered by each unique MotionEvent. After appending the view to the list the function checks to see if both expected views are present. If so, trigger the invisibility calls.
Instead of using onTouch use the onTouchEvent method and determine if the getX(index) getY(index) coordinates fall inside the two views you are expecting to be touched.
Your phone does not support multitouchEvent.That's why if you are touching two text view at a time, its taking only one event at a time and one textviewbecame invisible.
so first check whether your phone support more then one touch at time.
I have read a few questions regarding this topic on SO but haven't really found a solid answer to it.
I have a framelayout that I stack multiple custom views on, however the onTouch event only works with the top view. (the custom views are all the same view with the same onTouch event, just multiple of them)
FrameLayout
customView[2] <--- this is the last view added and the only one that receives the event
customView[1]
customView[0]
I'm testing it on Android 2.2 and am wondering if there is any way for the other views below to know where the touch happened?
EDIT (Adding some code)
I'm adding some code to hopefully help explain where I'm running into issues. At first I just automatically had the onTouchEvent return true. This made it so that the last view (in my case customerView[2]) would be the only one generating a value.
However, once I added the method to set the onTouchEvent to return true or false, now the only view returning a generated value is customView[0].
I hope this clears up what I am asking. I'm rather new to this and I appreciate you taking the time to explain it (and of course I appreciate your patience).
Also, I realize that my TextView's don't update with the value on each touchEvent, I'm working on fixing that.
My Activity:
public class MyActivity extend Activity {
CustomView[] customView;
TextView[] textView;
int numViews 3;
//FrameLayout and Params created
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
for(int i = 0; i < numViews; i++) {
customView[i] = new CustomView(this, i);
//Allows the onTouch to be handled by all Views - View[0] is the bottom view
if(i == 0) {
customView[i].setTouchBool(true); //set view's onTouch to return true
} else {
customView[i].setTouchBool(false); //set view's onTouch to return false
}
//Set TextView to display the number generated by the CustomView
textView[i].setText(Double.toString(customView[i].getGeneratedNumber()));
//Add views to main layout
frame.addView(textView[i]);
frame.addView(customView[i]);
}
}
}
My View:
public class CustomView extends View {
boolean onTouchHandler = true;
int xVal = 0, yVal = 0;
int index;
double generatedNum = 0;
public CustomView(Context context) {
this(context, 0);
this.index = 0;
}
public CustomView(Context context, int index) {
super(context);
this.index = index;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch(action) {
case MotionEvent.ACTION_DOWN: {
//do logic
}
case MotionEvent.ACTION_MOVE: {
//do logic
}
case MotionEvent.ACTION_UP: {
xVal = (int) ev.getX();
yVal = (int) ev.getY();
generateNumber(xVal, yVal, index);
break;
}
}
return onTouchHandler;
}
private void generateNumber(int x, int y, int index) {
if(index == 0) {
generatedNum = (x / 2) * (y / 2) + 64;
} else {
generatedNum = (x / 2) * (y / 2) + (index * 128);
}
}
public double getGeneratedNumber() {
return generatedNum;
}
public boolean setTouchBool(boolean b) {
this.onTouchHandler = b;
}
}
Android will cascade down the views calling onTouchEvent on each one until it receives a true from one of them. If you want a touch event to be handled by all of them, then return false until it reaches the last one.
EDIT:
Ok. If I understand correctly, you have a single top view containing a bunch of child views one layer deep. My original answer was assuming that you had three custom views that were on top of each other in the ViewGroup's hierarchy (View3 is a child of View2. View2 is a child of View1. View1 is a child of ParentView). You want the user's touch event on the parent view to get sent to all of it's children.
If that's the case, AFAIK, there is no view in Android's API that allows that. So, you'll have to make a custom view that does it.
OK, I haven't tested this, so please tell me if it works and if it's what you're trying. Create a custom class that extends whatever object frame is, then override the onTouch method like so.
#Override
public boolean onTouchEvent(MotionEvent ev) {
for(int i = 0; i < this.getChildCount(); i++){
this.getChildAt(i).dispatchTouchEvent(ev);
}
return true;
}
Now, keep the same logic that your custom views have, except they should all return false because your parent view will not receive the onTouch event unless they do as stated in my previous answer
note: with this implementation, the child view that the user actually touches will fire twice because the logic will go
fire child touch event -> return false -> fire parent touch event -> fire child touch event again
I know this question is very old, but I had the same problem and solved it by creating my own Layout to determine which child is actually touched.
I therefore iterate over the children of my custom layout and check if the user actually clicked on the view. The collision detection is handled in the custom view's onTouch() method. (Collision detection is done by intersecting a Region() with the event's x,y coordinates. For me this was convennient because I drew the custom view with a Path())
Here is a kotlin code snippet from my custom layout for better understanding:
class CustomLayout(context: Context, attrs: AttributeSet) :
RelativeLayout(context, attrs){
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
if(ev.action != MotionEvent.ACTION_UP){
return true
}
//Iterate over child view and search for the right child that should handle this touch event
for (i in childCount - 1 downTo 0) {
val child = getChildAt(i)
if (!viewTouched(child, ev)) {
continue
}
//Do something
Timber.d("Touched view: ${child.id}")
}
return true
}
private fun viewTouched(child: View, ev: MotionEvent) : Boolean {
child as OnTouchListener
//onTouch() does the collision detection
return child.onTouch(child, ev)
}