Let's say I got a fragment (of a ViewPager) with an EditText and I got somewhere this line of code: myEditText.setError("there is an error");
Now I want to know if I should allow the user to move forward to the next page. How can I check if there are any validation errors in the ENTIRE fragment (not just the single edit text from my example) in order to determine if the user can move to the next page? something like if(!this.containsErrors()) return true;
you can check the return value of TextView's getError():
Returns the error message that was set to be displayed with
setError(CharSequence), or null if no error was set or if it the error
was cleared by the widget after user input.
if (TextUtils.isEmpty(myEditText.getError()) {
// go ahead, no error set!
return true;
}
a simple routine to make the check could be:
boolean isErrorFree() {
ViewGroup viewGroup = (ViewGroup) getView();
if (viewGroup == null) {
return true;
}
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View view = viewGroup.getChildAt(i);
if (view instanceof TextView) {
if (!TextUtils.isEmpty(((TextView)view).getError()) {
return false;
}
}
}
return true;
}
check for typo
Related
according to https://developer.android.com/training/tv/start/navigation
You should set up the navigation order as a loop, so that the last
control directs focus back to the first one.
I have tried to implement such behavior for RecyclerView items:
fun RecyclerView.loopFocusVertically() {
viewTreeObserver.addOnGlobalLayoutListener {
val children = getAllChildren()
val firstFocusableItem = children.firstOrNull { it.isFocusable }
val lastFocusableItem = children.lastOrNull { it.isFocusable }
firstFocusableItem?.let { it.nextFocusUpId = lastFocusableItem?.id ?: it.id }
lastFocusableItem?.let { it.nextFocusDownId = firstFocusableItem?.id ?: it.id }
}
}
So on layout change i made first view have nextFocusUpId reference to last view, and the last view nextFocusDownId reference to first view. But it only works in case all views are actually layouted on screen, which obviously is not true in general case. RecyclerView actually recycles views, so last and first view may not exist at the same time on screen
How to implement focus loop for rv in leanback mode?
I have tried to use androidx.leanback.widget.VerticalGridView from leanback library, but it also doesn't have required behavior.
By default on pressing up from first focusable item of rv focus jumps to some other focusable view outside of rv, which is not so appropriate
By default on pressing up from first focusable item of rv focus jumps to some other focusable view outside of rv, which is not so appropriate
First, as for the UX, it's really problematic if you have views above your list so pressing up will really be an issue if to scroll to the last item as you want or to go to the above view.
With that said I'd suggest you give the loop option only from bottom in this case if you have a view above the list that should also get focus.
In general, I suggest you check VerticalGridView/HorizontalGridView for adding rows and lists in a lean-back app.
In the example I adjusted it to use a RecyclerView for your question.
For doing so you'll have to catch the button event, check if its the last item and if so, request focus to the first item.
#Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP) {
if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
if (onDownClick(event)) {
return true;
}
}
}
return super.dispatchKeyEvent(ev);
}
public boolean onDownClick(KeyEvent event) {
int currentIndex = 0;
if (mAdapter != null) {
currentIndex = mAdapter.indexOf(mCurrentItem);
}
if (currentIndex == mAdapter.size() - 1) {
mListRecycler.scrollToPosition(0);
if (mListRecycler.findViewHolderForAdapterPosition(0) != null) {
mListRecycler.findViewHolderForAdapterPosition(0).itemView.requestFocus();
}
return true;
}
return false;
}
In my android app
I need to check whether a particular view is focussed.
Now I found the getCurrentFocus() function in Activity class
but this returns a View Object.
How can I compare and check whether this returned View is same as the one in question.
I mean there is no getName() function here.
So after getting the View object, how can I compare to check which View class is this ?
The View.isFocused() method tells whether the view in question is focused or not.
if (myView.isFocused()) {
// your code
}
If you still want to use the getCurrentFocus() method, you can simply check:
View focusView = getCurrentFocus();
if (myView == focusView) {
// your code
}
Or else, you can compare your views by id.
View focusView = getCurrentFocus();
if (focusView != null && myView.getId() == focusView.getId()) {
// your code
}
You could use the getId() method I guess?
e.g.
getCurrentFocus() == R.id.myView;
getCurrentFocus() on the parent view
http://developer.android.com/reference/android/app/Activity.html#getCurrentFocus%28%29
No need for getName(). Just use the == operator. See:
View myView = findViewById(...);
if (activitygetCurrentFocus() == myView)
// ha focus
}
Another option, one I usually prefer, is to set a focus listener for the views you are interested in monitoring:
myView.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
// got focus logic
}
}
});
I have a listview which has an imageview inside each list item, when user click on that imageview, it will pop-up a menu.
It works very well on a normal android device with a touch screen.
But now I want to support google-tv, which app should be control by D-pad.
When I use D-pad to navigate through listview, only the entire row can be focus, I can't get the imageview inside listview be focus.
I try to add android:focusable="true" to imageview, but it cause the entire listview can't receive onItemClick event.
Does anyone know how to move the focus between listview rows and item inside listview using d-pad also keeps listview and imageview clickable?
Thanks a lot!
You have to set the following for your ListView:
listView.setItemsCanFocus(true);
With that focusable views inside of your listItems won't be ignored.
I got my Listview work with D-pad which can switch focus inside a list item.
This is how I solve it.
First, let your list view is item focusable.
NOTE: If you trying to set ItemsCanFocus to false later in your code, your will find your list item can't get focus anymore even if your set back to true again, so don't do that.
mDpadListView.setItemsCanFocus(true);
Then you need a field to keep tracking which list item is current selected.
Here I put the ViewHolder in the listItem's tag in Adapter.
mDpadListView.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
if (view.getTag() != null) {
DpadListAdapter.ViewHolder holder = (ViewHolder) view.getTag();
if (holder.shortCut != null && holder.shortCut.isShown()) {
currentSelectView = view;
} else {
currentSelectView = null;
}
} else {
currentSelectView = null;
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
Third, override onKeyDown() in Activity Method to control up, down, left, right key for D-pad.
When user press right button on D-pad, I let the listview to clearFoucs() and let the ImageView inside to obtain focus.
When user press up, down or left, the ImageView in list item will clear it's focus and the listView obtain focus again.
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (currentSelectView != null) {
DpadListAdapter.ViewHolder holder =
(ViewHolder) currentSelectView.getTag();
mDpadListView.clearFocus();
holder.shortCut.setFocusable(true);
holder.shortCut.requestFocus();
return true;
}
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
if (currentSelectView != null) {
DpadListAdapter.ViewHolder holder =
(ViewHolder) currentSelectView.getTag();
if (holder.shortCut.hasFocus()) {
holder.shortCut.clearFocus();
holder.shortCut.setFocusable(false);
mDpadView.requestFocus();
return true;
}
}
break;
default:
break;
}
return super.onKeyDown(keyCode, event);
}
You can use the properties to access the D-pad as below in your layout file:
android:nextFocusLeft="#+id/abc"
android:nextFocusRight="#+id/xyz"
android:nextFocusUp="#+id/pqr"
android:nextFocusDown="#+id/mno"
android:nextFocusForward="#+id/finish"
This all the properties will help you to access the D-pad in Google-TV.
I use a SlidingDrawer as my main layout. Inside the content area I have a Fragment (which contains a ListView) When the activity first loads everything is great, the listview scrolls correctly.
When I start a different activity and then come back, the first scroll motion I try is intercepted by the SlidindDrawer, and either opens or closes it. As soon as you stop the scroll and pick up your finger, the ListView is again able to scroll.
I would like the ListView to be able to scroll when the activity resumes. And just generally be able to control whether the SlidingDrawer is the one getting focus.
UPDATE:
I have narrowed the issue down a little bit. I have extended the SLidingDrawer to allow for click on buttons in the handle with the following code.
Override
public boolean onInterceptTouchEvent(MotionEvent event) {
super.onInterceptTouchEvent(event);
if (mHandleLayout != null) {
int clickX = (int) (event.getX() - mHandleLayout.getLeft());
int clickY = (int) (event.getY() - mHandleLayout.getTop());
if (isAnyClickableChildHit(mHandleLayout, clickX, clickY))
return false;
}
return super.onInterceptTouchEvent(event);
}
private boolean isAnyClickableChildHit(ViewGroup viewGroup, int clickX, int clickY) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View childView = viewGroup.getChildAt(i);
if (TAG_CLICK_INTERCEPTED.equals(childView.getTag())) {
childView.getHitRect(mHitRect);
if (mHitRect.contains(clickX, clickY))
return true;
}
if (childView instanceof ViewGroup && isAnyClickableChildHit((ViewGroup) childView, clickX, clickY))
return true;
}
return false;
}
If I comment out the onInterceptTouchEvent function, everything seems to work normally.
I noticed that you are calling super.onInterceptTouchEvent(event) twice. Why?
That could be the reason for the issue.
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)
}