BindingAdapter with lambda as argument - java

With Android databinding, I can do the following:
<View
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{() -> viewModel.onClick()}" />
My ViewModel does not have to implement OnClickListener, but just have a method:
public void onClick() {
}
What it passes to the onClick attribute in the Xml looks like a lambda to me.
How can I do this with my own BindingAdapters?
What I want:
Let's assume I want to bind touch events and I want to pass the MotionEvent, I would imagine this to look in the Xml like this:
<View
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:onTouch="(view, event) -> viewModel.onTouch(event)" />
and the BindingAdapter something like:
#BindingAdapter("onTouch")
public static void onTouch(View view, ??? lambda) {
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(final View v, final MotionEvent event) {
return lambda(event);
}
});
}
I do not want my ViewModel to implement OnTouchListener and bind it like:
#BindingAdapter()
public static void onTouch(View view, View.OnTouchListener onTouchListener) {
view.setOnTouchListener(onTouchListener);
}
and I do not want bind the touch event directly to my ViewModel like:
#BindingAdapter()
public static void onTouch(final View view, final MyViewModel myViewModel) {
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(final View v, final MotionEvent event) {
return myViewModel.onTouch(view, myViewModel);
}
});
}
Is this possible with Databinding?

As you want it to work like the built-in onClick, let's take a look at the official binding adapter for onClick attribute:
#BindingAdapter({"android:onClick", "android:clickable"})
public static void setOnClick(View view, View.OnClickListener clickListener, boolean clickable) {
view.setOnClickListener(clickListener);
view.setClickable(clickable);
}
As you can see, the binding adapter doesn't take a lambda as argument, it takes a listener. But you can pass a lambda to it in xml (since it is a single method interface)
So here is the binding adapter you need: (I chose another attribute name, since onTouch is already used by framework)
#BindingAdapter("onImageTouch")
public static void onImageTouch(View view, View.OnTouchListener onTouchListener) {
view.setOnTouchListener(onTouchListener);
}
This is how you use it in xml:
app:onImageTouch="#{(view, event) -> viewModel.onImageTouch(event)}"
And here is the corresponding method in viewmodel:
public boolean onImageTouch(MotionEvent event){
Timber.e("OnImageTouch is called");
return true;
}
(It does nothing, but notice that it should return a boolean because onTouch callback returns a boolean.)
You don't need to implement OnTouchListener in your viewmodel or activity. This works very similar to the example with onClick that you gave in the beginning.
Let me note that there is already a built-in binding adapter for onTouch attribute, but I guess it was just an example and you in fact want to learn how to make custom ones. But be careful not to choose attribute names that already exist in the framework, to avoid any clashes.

You can use a lambda with your own method if it returns a boolean, and use the attribute app:onTouchListener. No custom BindingAdapter needed.
<View
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:onTouchListener="(view, event) -> viewModel.onTouch(event)" />
android:onTouch also works but you get an Unknown attribute lint warning

It is possible to pass data to your view model however you also have to pass view along with data. so in your case. what you need to do is as follow.
<View
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{() -> viewModel.onClick(view.someData)}" />
And in your View Model.
public void onClick(View v,String someData) {
}

Related

How do I communicate between a class and a fragment which uses it?

I'm using Android Studio. I haven't been able to find an answer online, so even a link to a solution would be helpful.
I have an Activity, which includes a number of Fragments. One of these Fragments is called BookGridFragment, which uses a class called BookGrid.
BookGridFragment looks like this (I've left out irrelevant bits):
public class BookGridFragment extends Fragment {
BookGrid myBookGrid;
public BookGridFragment() {}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
// Inflate layout
View rootView = inflater.inflate(
R.layout.fragment_book_grid, container, false);
myBookGrid = rootView.findViewById(book_grid);
return rootView;
}
public void setBook(Book thisBook) {
myBookGrid.setBook(thisBook);
}
}
The BookGrid class is:
public class BookGrid extends View {
private Book mBook;
public BookGrid(Context thisContext, AttributeSet attrs) {
super(thisContext, attrs);
}
public void setBook(Book newBook) {
mBook = newBook;
}
protected void onDraw(Canvas canvas) {
if (mBook == null) return;
canvas.save();
draw_book_details();
// draw_book_details() is a function which just takes
// the book info and displays it in a grid
canvas.restore();
}
public boolean onTouchEvent(MotionEvent event) {
// This function responds to the user tapping a piece of
// book info within the grid
// THIS IS WHERE I'M HAVING PROBLEMS
}
}
So, that all works fine. The issue is, that I need the BookGridFragment to know when the user touches the BookGrid and to pass that information to another Fragment (via the Activity). So, I assume that when the onTouchEvent is reached, that should somehow notify the BookGridFragment that the BookGrid was touched, but I can't figure out how to do that.
Everything I've found online is about passing information between Fragments, but that approach doesn't work here as the BookGrid class doesn't "know" that it's within a BookGridFragment.
You could use the same idea that is used to communicate a Fragment and an Activity. Create an interface:
public interface OnBookGridTouched{
void onTouchGrid();
}
Add a variable to your BookGrid:
private OnBookGridTouched mCallback;
Add a setter to this variable:
public void setCallback(OnBookGridTouched callback){
mCallback = callback;
}
Then make your fragment implement the interface:
public class BookGridFragment extends Fragment implements OnBookGridTouched {
You'll be forced to implement the method onTouchGrid
In your fragment onCreateView pass the fragment to your custom view:
myBookGrid.setCallback(this);
Finally, in your custom view you can call the callback to reference the fragment:
public boolean onTouchEvent(MotionEvent event) {
// This function responds to the user tapping a piece of
// book info within the grid
// THIS IS WHERE I'M HAVING PROBLEMS
mCallback.onTouchGrid();
}
A solution could be to set the onTouch/onClick listener in the fragment instead of in the BookGrid itself. From there you can use the fragment method getActivity() to call an activity method, parsing on the correct data to the correct fragment.
I think this situation is very similar to a Fragment containing a Button.
The Button has a method which accepts something implementing a certain interface (for the Button: View.OnClickListener). The Fragment calls that method (for the Button: setOnClickListener()) to pass in the desired Object implementing all the required methods, either an anonymous class or maybe a field or the Fragment itself. There are pros and cons for all three approaches, it depends on your situation which one is best.
They have in common that BookGrid should have an interface as well as a method so other classes can set the current Object implementing that interface.
I am not quite sure about the exact scenario that you are having there. However, if the problem is the communication between the fragment and an activity which hosts the fragment, then you might think of the following implementation.
Let me point out some of your concerns first.
Everything I've found online is about passing information between
Fragments, but that approach doesn't work here as the BookGrid class
doesn't "know" that it's within a BookGridFragment.
BookGrid class will know the context of its existence when you will pass the Context towards it while calling a function of it. So I would like to suggest passing the context of the Activity or Fragment when you are calling a function from your BookGrid class.
public class BookGrid extends View {
private Book mBook;
private Context context;
public BookGrid(Context thisContext, AttributeSet attrs) {
super(thisContext, attrs);
this.context = thisContext;
}
public void setBook(Book newBook) {
mBook = newBook;
}
protected void onDraw(Canvas canvas) {
if (mBook == null) return;
canvas.save();
draw_book_details();
// draw_book_details() is a function which just takes
// the book info and displays it in a grid
canvas.restore();
}
public boolean onTouchEvent(MotionEvent event) {
// Call the function of your host activity
((YourActivity)(thisContext)).onBookGridTouched();
}
}
Now write a public method in your activity class which hosts the fragment named onBookGridTouched.
public void onBookGridTouched() {
// Communicate with other fragments here
}
However, a noble approach of solving this problem in a more generic way is to use an interface and then implement the interface wherever necessary like #LeviAlbuquerque suggested.
I am just putting another workaround which is a bit static.
Assuming that you have ONE Activity responsible of all fragments:
1.Create an interface in your BookGrid:
public interface ActionHappened {
void onActionHappened();
}
2.Create an instance of your interface within your BookGrid class and trigger the method onActionHappened where you want it to be triggered. For instance, if you would like it to happen in your onDraw(), then do the following:
ActionHappened actionHappened;
protected void onDraw(Canvas canvas) {
if (mBook == null) return;
canvas.save();
draw_book_details();
// draw_book_details() is a function which just takes
// the book info and displays it in a grid
canvas.restore();
actionHappened.onActionHappened();
}
3.Implement your interface within your activity
public class ActivityA extends AppCompatActivity implements BookGrid.ActionHappened {}
4.Within your implemented method, trigger the method:
#Override
public void onActionHappened() {
Fragment fragmentA = getSupportFragmentManager().findFragmentByTag(R.id.fragmentA);
Fragment fragmentB = getSupportFragmentManager().findFragmentByTag(R.id.fragmentB);
//Trigger that method from your activity to fragmentA or fragmentB
fragmentA.doWork();
fragmentB.doWork();
}
Wether you would like to pass data to fragmentA or fragmentB, doWork() method will do that for you. Make you create such a method in the corresponding fragment.

How can i add setOnClickLister method for this Library in Android?

I want to use this library in my application: https://github.com/Devlight/InfiniteCycleViewPager
I imported and added this library in my application and I set it.
But I don't know how can I use the setOnClickListener() method for these viewPager items?
For instance, when I click on item1, go to this item: fragment
How can I do that?
You have to do some changes in this library go through this link1 and sample of that library Where u can change for on click link2
Just set a page change listener for your InfiniteCycleViewPager and it will call the method onPageSelected(int position) when click on view.
YourInfiniteCycleViewPager.addOnPageChangeListener(this);
To retrieve the right position for the InfiniteCycleViewPager you should call getRealItem() when onPageSelected called.
You have to create an adapter for your InfiniteCycleViewPager, for example YourAdapter.java which extends PagerAdapter. Then set this adapter to
yourInfiniteCycleViewPager.setAdapter(mYourAdapter);
In override method: instantiateItem(ViewGroup, int), you can simply add your onClick listener and add logic to it:
#Override
public Object instantiateItem(ViewGroup container, fianl int position) {
....
View view = LayoutInflater.from(mContext).inflate(R.layout.xxx_your_layout, null);
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO
// do your code, and you can use variable 'position' here.
}
});
}

android - make textView invisible

When my app starts, the user needs to touch on the screen before the real action starts. I have a textView which gives the hint to touch the screen.
After the screen is touched, I want the text to get invisible. Right now the textView never disappears and always stays in the front.
public class MainActivity extends Activity implements OnGestureListener
{
public boolean touched = false;
TextView mMyView;
public void onTouch()
{
mMyView.setVisibility(View.GONE);
touched = true;
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
mMyView = (TextView)findViewById(R.id.textView6);
if(touched == true)
{
}
}
}
1.Always use if(something) if you want to see if it's true/false instead of writing if(something == true) [something is a boolian assigned with value true.]
2.If you point your views xml to a method using android:onClick like below,
<Button android:id="#+id/mybutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click me!"
android:onClick="onTouch" />
.
What's the point of implementing OnGestureListener?
If i do this onCreate i initialize my view
View myView = findViewById(R.id.my_view);
3.If i really want a touch i will do this
myView.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
// ... Respond to touch events --> tv.setVisibility(View.INVISIBLE);
return true; // if you return false with this then the listener will not be called for the subsequent ACTION_MOVE and ACTION_UP string of events.
}
});
Now you can see in the 3rd ones parameter there is a MotionEvent, you can identify the motion ACTION_DOWN , ACTION_MOVE and ACTION_UP
Now think have you ever used them. You got an idea in your head about a touch so tried to use touch events .. But you don't use them. So it does the same as what onClickListner does in your case. If you want motions use that 3rd one i gave.
But simply you can use
// view is the background layout
myView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Do something here --> Hide your text tv.setVisibility(View.INVISIBLE);
}
});
Those view onClickListner or setOnTouchListener you can directly use them inside onCreate or keep them inside a method and you can call that method from onCreate. Why to keep a boolean? It's nothing major
Note i considered myView as the background layout not your textView , background is the one you click / touch
So now you changed the questions code several times and I hope it´s the final change. Only than my answer could help.
You have done this in your onCreate():
if(touched == true)
{
tv.setVisibility(View.INVISIBLE);
}
But this is executed directly and has nothing to do with you onTouch() method. Let´s assume your onTouch() works correctly. Make the TextView global:
TextView mMyView;
initialize it in onCreate():
mMyView = (TextView)findViewById(R.id.textView6);
and then hide it in onTouch():
onTouch(View view){
mMyView.setVisibility(View.GONE);
}
But you have to be sure that your method onTouch() works. You can make a Toast or a Log to check. You have to be sure that:
-The TextView is inside your layout xml that you set with setContentView(R.layout.activity_game);
-The onTouch() method is declared in your TextView's xml attribute
android:onClick="onTouch"
and set clickable of your TextView to true:
android:clickable="true";
EDIT
If you implement onGestureListener() I guess the touch event is consumed by the listener and your TextView did not recognize onTouch(). If you don´t do any gesture detection in your activity, then remove this implementation.
You are checking if screen was touched in onCreate() which is called only once at the start of the activity. Initialize TextView globally and set its visibility inside onTouch(View v, MotionEvent event)
Also your onTouch() isn;t correct. You should override public boolean onTouch(View v, MotionEvent event)
public class MainActivity extends Activity
{
public boolean touched = false;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
TextView tv = (TextView) findViewById(R.id.textView6);
tv.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
touched = true;
tv.setVisibility(View.INVISIBLE);
return true;
}
});
}
}
Instead of implementing OnGestureListener add a setOnTouchListener in your root view of your activity layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rlTestView"/>
For example rlTestView is your activity's root layout id, then use below code in your oncreate method
((RelativeLayout)findViewById(R.id.rlTestView)).setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
tv.setVisibility(View.GONE);
return true;
}
});`
Use the code below on the onCreate method and yes set the visibility as GONE instead of invisible. Also state the current visibilty of the TextView in the onTouch then set it to
tv.setVisibility(View.GONE);

Android Button listener implementation

Hey guys i'm new in Android's dev and for a project, I have to implement a listener for a button.
But unfortunately, he can't detect the button I think.
Here is the java code :
public class Touch extends AppCompatActivity implements OnTouchListener,OnClickListener {
private button boutonCompteur = null;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.id.Compteur);
boutonCompteur = (button) findViewById(R.id.Compteur);
boutonCompteur.setOnTouchListener(this);
boutonCompteur.setOnClickListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent event)
{
return true;
} }
and here is the XML :
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="appuyez ici pour compter votre nombre de touch"
android:id="#+id/Compteur"
android:height="130dp"
android:textColor="#1818e3"
android:textColorHighlight="#cd5555"
android:textSize="22dp"
android:textStyle="italic"
android:layout_marginTop="150dp"
android:layout_alignParentStart="true" />
Thanks for your answers :)
I am going to use some visual help to answer your question and identify why is not working...
take a look at the image below and note:
You need a layout for your activity and this is not the same as the button
You need to find the button in the layout, that is odne by the id you used in the xml file, and is not the same as the Layout!!
you need to set the rigth listener to get the onclick, android has for that the OnClickListener interface
the word button between parenthesis is a casting, (android will try to convert something to a class, therefore the class is the class button, and not the name of your variable)
all the code that you write inside the onClick is what will be executed once the button is pressed.
Conclusion
Take the image as a reference and implement it in your code.
Remove onTouch , use onClick only.
Remove this line
boutonCompteur.setOnTouchListener(this);
and change public boolean onTouch(View v, MotionEvent event) to
public void onClick(View v) {
// Do whatever you want
}
And also change private button boutonCompteur = null; to private Button boutonCompteur; . (I don't think you need to add =null when doing button declaration).

How to get click, double tap, and long click gestures for a view inside a gridview?

I have ImageViews inside of a GridView, I had been using an OnItemClickListener along with an OnItemLongClickListener set on the GridView to open the image on a larger page and to delete the item respectively. Now, I have to implement rearranging of the ImageViews in the GridView, so I plan to move the deletion function to a double tap gesture, (please do not lecture me on android style guidelines (including the possibility of contextual actionbars, which I suggested), as this is what my boss asks for to emulate functions inside our ios app) in order to reserve long click for the drag and drop. I set an OnTouchListener on each view in the getView of my custom adapter, feeding a GestureDetecter with a listener extending SimpleOnGestureListener the given MotionEvent with onTouchEvent. I know what to do up to that point, but when I included (onDown of course, to get other callbacks) onDoubleTap, onSingleTapConfirmed, and onLongPressed all taps were interpreted as long clicks. And when I removed the both callback methods to be replaced with their listener counterparts once again (ie OnItemClickListeners) I received those two gestures but not the double tap, which makes sense, as double taps start out as a single tap unless you wait for a bit less than a second to confirm them as singles rather than potential doubles. I also tried placing the OnItemClickListener, but not the OnItemLongClickListener, with the callback in the extended SimpleOnGestureListener. In this case, only long presses were ever interpreted, but other gestures caused no response. Here is my code as it stands now, and do note that I returned false in the onTouchEvent in order to allow others (itemclicklisteners) to consume the events following the attempts made in the GestureDetector.
public class MainBoardGridAdapter extends GenericBoardGridAdapter implements OnItemLongClickListener {
private class Ges extends GestureDetector.SimpleOnGestureListener {
int pos;
public Ges(View v) {
pos = (Integer) v.getTag();
}
#Override
public boolean onDown(MotionEvent me) {
//this does get called but none of these methods below
return true;
}
#Override
public boolean onDoubleTap(MotionEvent me) {
new DeleteConfirmationPrompt(c, "board") {
#Override
protected boolean onDeleteConfirmed() {
// delete the visionboard
return deleteBoard(pos);
}
}; // Constructor shows dialog
return false;
}
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
MainBoardGridAdapter.super.flagForUpdate(pos);
if (listener != null) {
listener.onBoardClick(pos, getName(pos));
} else {
Intent intent = new Intent(c, VisionBoardActivity.class);
intent.putExtra(VisionBoardActivity.EXTRA_VISION_BOARD_NAME, getName(pos));
frag.startActivityForResult(intent, MyBoardsFragment.REQUEST_EDIT);
}
return false;
}
}
#Override
public boolean onItemLongClick(AdapterView<?> parent, View v,
final int pos, long id) {
Toast.makeText(c, "Long", Toast.LENGTH_LONG).show();
return false;
}
// called by getView of extended adapter
#Override
public void onImageLoaded(ImageView iv, String data, View root) {
iv.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
(new GestureDetector(c, (new Ges(v)))).onTouchEvent(event);
return false;
}
});
}
}
And in the Activity, gv is my GridView:
gv.setOnItemLongClickListener(gridAdapter);
Also note that I had been using true in the return value in the GestureDetector methods, until trying the current configuration.There was no difference to be seen.
Thank you for your valuable time and help, I hope that someone will be able to point out what I am doing incorrectly.
-Jackson

Categories

Resources