I was using MVP in a previous application with my View component essentially an RelativeLayout.
Anytime I would want to block any touch interaction on the RelativeLayout (for example while a network access) I would return true from touchIntercept like this.
public abstract class RootView<T> extends RelativeLayout implements BaseView<T> {
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mIsScreenLocked)
return true;
else
return super.onInterceptTouchEvent(ev);
}
#Override
public void showProgress(boolean show, boolean lockScreen) {
ProgressBar progressBar = ((ProgressBar) findViewById(R.id.progress_bar));
if (progressBar != null) {
if (show) {
progressBar.setVisibility(View.VISIBLE);
} else {
progressBar.setVisibility(View.GONE);
}
}
mIsScreenLocked = lockScreen;
freezeBottomBar(show);
}
}
In my new application my views are Fragments , the base of which extends a fragment
public abstract class BaseFragment
{
}
I would like to achieve something similar , to block all touch interactions on the fragment when a user initiates any network access.
public abstract class BaseFragment
{
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mIsScreenLocked = true;
initializeControls();
attachListeners();
if (savedInstanceState == null)
onScreenInitializationComplete(getArguments());
else
onScreenInitializationComplete(getArguments(), savedInstanceState);
***createTouchInterceptor***(view);
//should we run ignition here?
}
private void createTouchInterceptor(View fragmentView) {
fragmentView.setOnTouchListener((view, motionEvent) -> {
if (mIsScreenLocked)
return true;
else
return false;
});
}
}
This ofcourse wont work , since when a button is pressed on the fragment , the button would receive the touch.
Any ideas?
I am not sure that this is the best idea from a UX point of view, since the user doesn't really know why his/her actions are doing nothing, but for the sake of answering the question you can either
Use the same solution as you did for the RelativeLayout with the top container ViewGroup in your fragment's view.
disable setEnabled(false) only the "clickable" views, like buttons...etc, other views like TextViews and such, don't need to be disabled
Use an overlay transparent view, a view that is fully transparent over your root view that consumes the events in case of your fragment being disbaled.
However, you might want to consider a different approach such as
Showing a blocking progress dialog
Only disabling the button in question and let the user touch other views
Related
I am displaying a FrameLayout with some components inside a Fragment. In onCreateView(..) I am animating the content of the FrameLayout and everything works fine. Now I want to animate the content before closing the Fragment.
In my current solution I am overriding onBackPressed() in the parent Activity and then I'm calling the method onBackPressed() inside my Fragment and animating the content there. The problem with this solution is, that I want to inflate the Fragment from various activities and then this is not really a nice solution... Does anybody know a better approach?
Thanks for your help!
Note:
I also tried to override onCreateView() and onPause() but the animation is not shown if I start it in those methods
and the following method does not fulfill my requirements either as it animates the whole fragment and I want to animate the content
getActivity().getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.slide_in_up, R.anim.slide_out_up, R.anim.slide_out_down, R.anim.slide_in_down)
Maybe you can try to handle onBackPressed in your fragment like below:
yourRootLayout.setFocusableInTouchMode(true);
yourRootLayout.requestFocus();
yourRootLayout.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
animateMyViews();
return true;
}
return false;
}
});
I wanted to perform sliding exit animation before dismissing the view/fragment.
These are the steps I performed:
I created a runnable task which can dismiss the current screen.
Passed that runnable to animating view.
Used view.postOnAnimationDelayed(runnable, 400) so that it can animate and execute the runnable after 400 milliseconds.
Also I made sure that my animation duration is >= 400 so that the transition is smooth.
Below is a little bit altered version of code for idea.
I used view.postOnAnimationDelayed(runnable, 400) to
Dialog dialog = new Dialog(getActivity(), getTheme()) {
#Override
public void onBackPressed() {
ParentFragment.this.onBackPressed();
Runnable runnable = new Runnable() {
#Override
public void run() {
//Referencing this class in runnable
getInstance().dismiss();
}
};
//webView is the child view loaded on this fragment
if(webView != null && webView.webViewClient != null) {
webView.webViewClient.animateClose(webView, runnable);
} else {
super.dismiss();
}
};
function animateOnClose in webViewClient looked like this:
public void animateClose(final WebView view, Runnable runnable) {
Animation anim = AnimationUtils.loadAnimation(getMainActivityContext(),
animationResource);
view.startAnimation(anim);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.postOnAnimationDelayed(runnable, 400);
// you can also try view.postOnAnimation(runnable);
} else {
runnable.run();
}
}
I think what you need is to notify the Fragment that you are about to destroy it.
A crude pseudocode might look like
myFragment.aboutToClose();
While in your Fragment's aboutToClose() method.
public void aboutToClose()
{
// Perform all the animations you want.
// Don't forget to add onAnimationEnd() call back.
onAnimationEnd()
{
// Notify Activity that Animations have completed.
callback.animationsCompleted();
}
}
Finally in your calling Activity.
public void animationsCompleted()
{
// Destroy fragment.
getActivity().getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.slide_in_up, R.anim.slide_out_up, R.anim.slide_out_down, R.anim.slide_in_down);
}
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 am making an alarm clock.
I want to make an activity which on the layout part is empty (exept a photo on the background)
I want to do, that if i touch anywhere on the screen, the music will stop.
I thought about making the img as a imageview...
but it dosent strach on the screen when I do it (even if the parameters are on the whole screen)
help?
in your layout verify that :
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
then try to use onTouchListener
then try :
yourActivityLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View arg0, MotionEvent arg1) {
// action to do
return true;//always return true to consume event
}
});
Do this way to put touch event on Whole Activity
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// do your work here
return true;
} else {
return false;
}
}
}
Have your Activity or Fragment implement OnClickListener, and then assign it as your click listener to every view and or layout.
in the first line of the function just run some logic,
#override
public void onClick(view v)
{
if(isMusicPlaying)
stopMusic();
// here run the rest of your logic
if (v == someButton){}
}
If you want if touch the layout , set click listener to the layout it self
You may play with WindowManager and overlaying your layout over everything else(may also overlay status bar and other system UI)
WindowManager instance has addVieW() method. With right layout params it produces described result
Try this, Pass your topmost parent as argument for this method
Eg: stopMusicOnTouch(yourParentView);
public void stopMusicOnTouch(View view) {
//Set up touch listener for non-text box views to hide keyboard.
if(!(view instanceof ImageView)) {
view.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
// Stop music here
return false;
}
});
}
//If a layout container, iterate over children and seed recursion.
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
View innerView = ((ViewGroup) view).getChildAt(i);
stopMusicOnTouch(innerView);
}
}
}
At the end,I made a button for the whole screen and did it transperent (but visible)
it worked perefectly, and I suggest it to others!
I have been trying to style some elements of a ListView. Mainly, on top of the color & background made by selectors, I would like to also change the font of the pressed item. This is not doable with a selector, so I have to do this by code.
I have tried many approaches, with no success.
simplified sample code to illustrate the issue :
in the adapter of the listview :
getView(..) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.mylayout);
viewHolder = new MyViewHolder();
...
convertView.setOnTouchListener(new MyTouchListener());
}
}
private class MyTouchListener implements onTouchListener {
#Override
public boolean onTouchEvent(MotionEvent event) {
..
switch (maskedAction) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
textView.setTypeface(BOLD);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
textView.setTypeface(NORMAL);
break;
}
return false; //// PROBLEM
}
Now, my issue is that if my touch listener returns false for the pointer down event, it means I did not consume the event, so I don't receive the subsequent event (move, up, ..). So the textview is stuck in the BOLD font.
If I return true for the onDown event, the framework considers that the event has already been handled for the view, so it ignores the selectors in my xml. Annoying, but I could always do all the styling in the code, even if it means losing the advantage of selectors for the proprieties that can use it. It also means that the item does not receive the click event, which is a deal breaker.
So, I am in a dead end here. I have tried many alternatives ways to do this (custom view which calls both my listener and the default one, different returns values, ...) with no success.
Does anybody have an idea on how to solve this ?
I have figured it out :
By design, this is not doable with an OnTouchListener. However, there are several other options.
One is to override dispatchTouchEvent or onInterceptTouchEvent in the parent ViewGroup.
Another is to override onCreateDrawableState.
Since I was already overloading this method (in order to create new states), I opted for that one :
#Override
protected int[] onCreateDrawableState(int extraSpace) {
{...}
if (mListener != null) {
if (isPressed() != isPressed) {
isPressed = isPressed();
mListener.onPressedStateChange(isPressed);
}
}
return super.onCreateDrawableState(extraSpace);
}
public interface PressedStateListener {
public void onPressedStateChange(boolean isPressed);
}
PressedStateListener mListener;
public void setOnPressedStateChangedListener(PressedStateListener listener) {
mListener = listener;
}
in the viewholder :
private class DropDownOnPressedStateChangedListener implements PressedStateListener {
#Override
public void onPressedStateChange(boolean isPressed) {
if (isPressed) {
getText().setTypeface(MEDIUM_TYPEFACE, Typeface.NORMAL);
} else {
getText().setTypeface(REGULAR_TYPEFACE, Typeface.NORMAL);
}
}
in the adapter :
if (convertView == null) {
view = mInflater.inflate(ENTRY_RESOURCE_ID, parent, false);
viewHolder = new HeaderDropDownViewHolder(view);
((CustomLayout)view).setOnPressedStateChangedListener(viewHolder.getOnListener());
That way I have a clean implementation that allows me to edit any view property when it is pressed or not without any conflict with its existing customization or behavior.
I have a Gallery and a custom layout for its items. There are two buttons on each. Every time I touch an item View, its buttons become pressed.
When I click a button, another one doesn't receive onPressed state, however both of them receive it when I click near by.
How to prevent changing state of untouched buttons?
Replace your Buttons in the item layout with the custom Button class below and see if the behavior still exists after:
public class SpecialButton extends Button {
// implement constructors
#Override
public void setPressed(boolean pressed) {
if (pressed && (getParent() instanceof View) && ((View) getParent()).isPressed()) {
return;
}
super.setPressed(pressed);
}
}
I've already had an overridden class as container where the buttons are. So the way it was mentioned by Luksprog I override setPressed()
public class GalleryItem extends FrameLayout {
#Override
public void setPressed(boolean pressed) {
return;
}
}