I created a recycler view where the items can be dragged. The following is from my adapter:
public void onBindViewHolder(final FootballPitchMainAdapter.ViewHolder holder, int position) {
holder.rvPlayerNameTop.setText("testing");
holder.linearLayoutFull.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
ClipData data = ClipData.newPlainText("", "");
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(
view);
view.startDrag(data, shadowBuilder, view, 0);
view.setVisibility(View.INVISIBLE);
return true;
} else {
return false;
}
}
});
Now this does work, I can drag the linearLayout, which contains an imageView and a texview. The drag allows me to move the items out of the recyclerview.
If I used motion, I couldn't move the linearlayout frame out of the RV.
But my problem is, once I drag the view, I want to be able to place it anywhere on the screen.
So in the class where the RV is created, I set the following:
pitchRelativeLayout.setOnDragListener(new MyDragListener());
class MyDragListener implements View.OnDragListener{
#Override
public boolean onDrag(View v, DragEvent event) {
switch(event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
//Do nothing
break;
case DragEvent.ACTION_DRAG_ENTERED:
break;
case DragEvent.ACTION_DRAG_EXITED :
break;
case DragEvent.ACTION_DRAG_LOCATION :
break;
case DragEvent.ACTION_DRAG_ENDED :
// Do nothing
break;
case DragEvent.ACTION_DROP:
View view = (View) event.getLocalState();
ViewGroup owner = (ViewGroup) view.getParent();
owner.removeView(view);
RelativeLayout container = (RelativeLayout) v;
container.addView(view);
view.setVisibility(View.VISIBLE);
break;
default: break;
}
return true;
}
}
PitchLayout is a relativelayout which covers the entire screen. The idea is just to drag an item from RV and drop it anywhere.
Here is an image which I hope explains this better:
I am trying to tell the relative layout to listen to the drag item and on the drop, place it here.
The above code is from online tutorials I have tried, answers from Stackover Flow
Related
I have trouble finding a good example on how to swipe between fragments with help of bottom navigatiom bar. Since FragmentStatePagerAdapter is deprecated and a new ViewPager2 is now recommended instead of the old ViewPager I want to use ViewPager2 and FragmentStateAdapter in my code instead. I have found an example of how to combine BottomNavigationBar and ViewPager here and I want to do something similar. My code have many similarities to the one in the example with the only difference that I have my code in a fragment instead of an activity. Here is a picture of how my FrontendFragment display look like. I can switch between the views using the bottomnavigationbar but I also want to be able to swipe between the views. Can someone help me or at least direct me on the right way? Here is my code:
FragmentPagerAdapter Class:
public class FragmentPagerAdapter extends FragmentStateAdapter {
private static final int mFragmentCount = 5;
public FragmentPagerAdapter(#NonNull Fragment fragment) {
super(fragment);
}
#NonNull
#Override
public Fragment createFragment(int position) {
switch (position){
case 0:
return new HomeFragment();
case 1:
return new SearchFragment();
case 2:
return new AddFragment();
case 3:
return new MessageFragment();
case 4:
return new ProfileFragment();
}
return null;
}
#Override
public int getItemCount() {
return mFragmentCount;
}
}
FrontendFragment Class:
public class FrontendFragment extends Fragment implements BottomNavigationView.OnNavigationItemSelectedListener{
private BottomNavigationView mBottomNavigationView;
private ViewPager2 mViewPager2;
private FragmentPagerAdapter mFragmentPagerAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_frontend, container, false);
loadFragment(new HomeFragment());
mBottomNavigationView = v.findViewById(R.id.bottomNavigationBar);
mBottomNavigationView.setOnNavigationItemSelectedListener(this);
return v;
}
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment selectedFragment = null;
switch (item.getItemId()) {
case R.id.home_icon:
selectedFragment = new HomeFragment();
break;
case R.id.search_icon:
selectedFragment = new SearchFragment();
break;
case R.id.add_icon:
selectedFragment = new AddFragment();
break;
case R.id.message_icon:
selectedFragment = new MessageFragment();
break;
case R.id.profile_icon:
selectedFragment = new ProfileFragment();
break;
}
return loadFragment(selectedFragment);
}
private boolean loadFragment(Fragment selectedFragment) {
if(selectedFragment != null){
MainActivity.sFm.beginTransaction().replace(R.id.relLayoutMiddle, selectedFragment).commit();
return true;
}
return false;
}
}
Thanks in advance!
As I'm already using BottomNav with ViewPager2 in one of my app, I can help.
Your code is partially correct which means your FragmentPagerAdapter is fine, but not your FrontEndFragment.
See, the FragmentPagerAdapter has to be set to a ViewPager2 as
//this here is the FrontEndFragment
mViewPager2.setAdapter(new FragmentPagerAdapter(this));
Then, You don't have to do the FragmentTransaction at all, you just have to change the ViewPager2's current item position through the BottomNavigationBar as
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.home_icon:
mViewPager2.setCurrentItem(0);
break;
case R.id.search_icon:
mViewPager2.setCurrentItem(1);
break;
case R.id.add_icon:
mViewPager2.setCurrentItem(2);
break;
case R.id.message_icon:
mViewPager2.setCurrentItem(3);
break;
case R.id.profile_icon:
mViewPager2.setCurrentItem(4);
break;
}
return false;
}
This is all, you don't have to deal with Fragments at all apart from the FragmentPagerAdapter. Also, don't forget to remove the loadFragment(new HomeFragment()); which is not required, nor the function loadFragment() is required.
(Optional), Furthermore, if you want to disable the Swipe Action of the ViewPager2 and want the Fragments to be selected based on the Selected BottomNav item only, then you can set setUserInputEnabled() property of ViewPager2 as false.
Next, to set the BottomNavigationBar's item as selected based on the swipe of the ViewPager2, what you've to do is,
Create a global var
MenuItem previousMenuItem;
Then, set a default item (first) to be selected of BottomNav on activity start as
mBottomNavigationView.getMenu().getItem(0).setChecked(true);
Finally, set an OnPageSelected() callback on ViewPager2 to update the selected Menu Item as:
mViewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if (previousMenuItem != null) {
previousMenuItem.setChecked(false);
}
else {
mBottomNavigationView.getMenu().getItem(0).setChecked(false);
}
mBottomNavigationView.getMenu().getItem(position).setChecked(true);
previousMenuItem = mBottomNavigationView.getMenu().getItem(position);
}
#Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
}
});
What you're doing here is that you're setting default item to previousMenuItem and then on swiping to a different page, deselecting the previousMenuItem and selecting the new one, which means updating the BottomNav based on ViewPager's current item. This is the complete code you require to acheive your objective.
If you want to swipe between views, a simple solution would be to store all views in your parent view, but set all layouts for views except the initial view to android:visibility="gone". Make sure to set the initial view layout to android:visibility="visible" though. Now on you button clicks, you will have to implement onClick such that they turn on/ off view visibilities accordingly. For example, store views in order and control them via array index. But the whole thing you're trying to do is generally not a good design pattern in my opinion.
Why don't you load another Activity onClick instead of crowding your single activity? This will cause load time issues. Even if the views are non-visible, it's just an overall hassle to maintain all that in one place.
I have a PopupWindow which is shown as a drop down when I click a Item on my ListView:
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
View v = getLayoutInflater().inflate(R.layout.popup_click_menu, null);
final PopupWindow mypopupWindow = new PopupWindow(v,300, RelativeLayout.LayoutParams.WRAP_CONTENT, true);
mypopupWindow.showAsDropDown(view, -153, 0);
}
The result is this:
My question is: I want to have a dynamic position of my pop up. So if I click on the middle of the Item then it should be shown in the middle. How can I do that?
You can solve this with a TouchListener:
list.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View view, MotionEvent event) {
positionX = (int) event.getX();
return false; // not consumed; forward to onClick
}
});
Then you have the exact x-position and you can move the PopupWindow by onItemClick:
mypopupWindow.showAsDropDown(view, positionX, 0);
I am using touch listener on my views to drag them. findViewById(R.id.myimage1).setOnTouchListener(new MyTouchListener());
Unfortunately there are lots of views therefore I have to stack them in a scrollview.
Here is the touch listener:
private final class MyTouchListener implements View.OnTouchListener {
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
ClipData data = ClipData.newPlainText("", "");
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(data, shadowBuilder, view, 0);
view.setVisibility(View.INVISIBLE);
return true;
} else {
return false;
}
}
}
Because of this touch listnener I am unable to scroll the listview.
When I try to scroll, the touch listener fires and view's shadow is created instead of scroll.
How can I use both events or can use Drag n Drop functionality in a ScrollView
I am trying to make a grid of image views, that when i drag another view over them(this view essentially acts as a mouse pointer), it changes the image I drag over in the grid to a different image. When I drag the "mouse" view away it will go back to normal.
Right now I have my mouse cursor view moving and have a grid of image views implemented with a grid view using a custom adaptor ( pictured below)
I can drag my cursor around but when ever I try to drag inside the gridview, it calls the image on the gridview's ontouch listener(only when i click on the image view, not when I slide over it). I also cannot seem to get onHover working on the images in the image View.
Also, if the mouse view is covering one of the grid images, the ontouchlistener of the image view the grid is not called.
What is the best way( types of views, etc) to use to go about implementing this?
Here is some relevant code from what I have so far.
This is where I set up my grid view in my activity
GridView gridView;
gridView = (GridView) findViewById(R.id.gridView);
gridView.setAdapter(new ImageAdapter(this,new String [25]));
gridView.setOnDragListener(new MyDragListener());
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v,
int position, long id) {
}
});
gridView.setClickable(false);
This is the getView function in my adapter
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View gridView;
if (convertView == null) {
gridView = inflater.inflate(R.layout.vibe_pad_section, null);
final ImageView imageView = (ImageView) gridView
.findViewById(R.id.grid_item_image);
imageView.setImageResource(R.drawable.vibepadcircle);
imageView.setPadding(10,10,10,10);
imageView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent e) {
Log.e("here","here");
switch (e.getAction()) {
case MotionEvent.ACTION_MOVE:
Log.e("here2","here2");
}
imageView.setImageResource(R.drawable.bar_high);
return true;
}
});
VibePad.padTargets.add(imageView);
} else {
gridView = (View) convertView;
}
return gridView;
}
class MyDragListener implements View.OnDragListener {
#Override
public boolean onDrag(View v, DragEvent event) {
final int action = event.getAction();
ClipData dragData;
View p = (View) v.getParent();
Object index = p.getTag();
final int which = Integer.parseInt(index.toString());
boolean handled = true;
Log.e(" in on drag","in on drag") ;
switch (action) {
case DragEvent.ACTION_DRAG_EXITED:
// Report the drop/no-drop result to the user
((ImageView) v).setImageResource(R.drawable.bar_high);
break;
case DragEvent.ACTION_DRAG_ENTERED:
// just drop it
((ImageView) v).setImageResource(R.drawable.vibepadcircle);
break;
default :
break;
}
return handled;
}
You need to create a class that extends OnDragListener:
When ACTION_DRAG_ENTERED occurs, you can change the underlying image to something else. When you exist (you've dragged the view outside the bounding box of the the image on your gridview) then you can change it back to it's original image
class MyDragListener implements OnDragListener {
#Override
public boolean onDrag(View v, DragEvent event) {
final int action = event.getAction();
ClipData dragData;
View p = (View) v.getParent();
Object index = p.getTag();
final int which = Integer.parseInt(index.toString());
boolean handled = true;
switch (action) {
case DragEvent.ACTION_DRAG_STARTED:
dragSku = event.getClipDescription().getLabel().toString();
break;
case DragEvent.ACTION_DRAG_ENDED:
// Report the drop/no-drop result to the user
final boolean dropped = event.getResult();
compareInMotion = false;
BaseAdapter lva = (BaseAdapter) gridview.getAdapter();
lva.notifyDataSetChanged();
break;
case DragEvent.ACTION_DROP:
// just drop it
((ImageView) v).setImageBitmap(emptyImg);
setUpCompareItem((ImageView) v, dragSku); break;
default :
break;
}
return handled;
I implemented a gesture detector to find out when the user will fling my listview (and when the last element of the list is in view) so that I may add more data to the list. The list has a custom adapter that I've built. Each row has a few textviews in it and an image button. I was using the imagebutton (which is an arrow) with a click listener in my adapter to open another activity related to the pressed row.
Management wants the user to be able to click anywhere on the row in order to activate it. So:
#Override
public View getView(final int position, View convertView, ViewGroup parent)
{
View v = convertView;
final ViewHolder viewHolder;
.....
//viewHolder.ibShipmentDetails.setOnClickListener(new OnClickListener()
v.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View view)
{
....
}
}
But now, my fling detector won't work correctly. Sometimes it responds and afterwards works correctly; restart the activity, again it doesn't work.
Here is my gesture detector:
class MyGestureDetector extends GestureDetector.SimpleOnGestureListener
{
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if((e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY))
{
if(!loading && ((totalItemCount - visibleItemCount) <= firstVisibleItem))
{
if(allTabSelected)
{
allPageNo++;
}
else
{
openPageNo++;
}
new GetShipmentPreviews().execute(1);
}
}
}
And in my activity:
gestureDetector = new GestureDetector(this, new MyGestureDetector());
gestureListener = new View.OnTouchListener()
{
public boolean onTouch(View v, MotionEvent event)
{
return gestureDetector.onTouchEvent(event);
}
};
listView.setOnTouchListener(gestureListener);
What should I do to keep v.setOnClickListener and the onFling() command intact?
Turns out my MotionEvents from onFling are null. This apparently happens because the event gets somehow "eaten" by the onClickListener from my custom adapter.
I got the solution from this answer : https://stackoverflow.com/a/7159738/1688731
I added the following code to my Activity class:
#Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
super.dispatchTouchEvent(ev);
return gestureScanner.onTouchEvent(ev);
}
You should use listViev.setOnItemClickListener() once, instead of calling v.setOnClickListener() for all list items.