xamarin forms snackbar custom view not working as expected (setting control values, unwanted action button, view not disapearing) - xamarin.forms

I am getting various unexpected behaviors when trying to implement a snackbar with a custom view in Xamarin forms.
This is the function I am calling (from a dependency service):
public void MySnackBar(string argument, string title, string message, string action_text, string action, MySnackBarPosition position)
{
Activity activity = Xamarin.Essentials.Platform.CurrentActivity; //.Current.Activity;
Android.Views.View view = activity.FindViewById(Android.Resource.Id.Content);
var snack_view = activity.LayoutInflater.Inflate(Droid.Resource.Layout.my_snackbar, (ViewGroup)view);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WrapContent,
FrameLayout.LayoutParams.WrapContent
);
snack_view.LayoutParameters = lp;
var snack_label = snack_view.FindViewById<TextView>(Droid.Resource.Id.my_snack_text);
snack_label.Text = "You're doing something wrong";
snack_label.Tag = $"{argument}:{action}";
snack_label.Click += OnSnackClick;
var snack = Snackbar.Make(activity, snack_view, "", Snackbar.LengthLong);
snack.Show();
}
private void OnSnackClick(object sender, EventArgs args)
{
var v = (View)sender;
var tag = v.Tag;
var data = tag.ToString().Split(':');
//var argument = data[0];
//var action = data[1];
MyApp.App.Instance.HandleSnackbarAction("","");
}
Layout file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="250dp"
android:layout_gravity="end"
android:orientation="horizontal"
android:layout_height="wrap_content"
android:padding="20dp"
android:background="#android:color/transparent">
<TextView
android:id = "#+id/my_snack_text"
android:elevation="8px"
android:background="#fff"
android:layout_marginEnd="20dip"
android:layout_marginTop="20dip"
android:padding = "20dip"
android:gravity="center"
android:text="hello hsdkfhdskjh dsfhsdjkh fjsdkfh sdjkhf dskj hfjkdshfjkdshfjkdsh"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
</LinearLayout>
When I call this code for a first time the TextView displays with the correct text, but there are two big issues:
An empty button appears in the top left of the screen, presumably a snackbar action button
The TextView doesn't disappear when the snackbar times out
The v.Tag inside the OnSnackClick is null
If I call the code a second time, the same issues persist but on this occasion the TextView's text isn't even set and only the default text from the xml file is displayed.
First Call:
Subsequent Calls:

Related

ListView ID called on null? (Kotlin)

I'm new to Kotlin and im trying to build a HomePage where there is a BottomNavigation with 3 Fragment pages but in 1 of the pages I set a ListView and whenever I call the ID it gives the following errer(Caused by: java.lang.IllegalStateException: categoryListView must not be null)
Here is how Im calling it in my HomePage which is where the content appears:
class HomePage : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
lateinit var adapter : CategoryAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home_page)
adapter = CategoryAdapter(this, DataService.categories)
categoryListView.adapter = adapter
navView.setNavigationItemSelectedListener(this)
bottomNavigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
replaceFragment(HomeFragment())
}
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when(item.itemId) {
R.id.Bottom_Nav_Home -> {
println("Home pressed")
replaceFragment(HomeFragment())
return#OnNavigationItemSelectedListener true
}
R.id.Bottom_Nav_Notifs -> {
println("Notification pressed")
replaceFragment(NotifsFragment())
return#OnNavigationItemSelectedListener true
}
R.id.Bottom_Nav_List -> {
println("List pressed")
replaceFragment(ListFragment())
return#OnNavigationItemSelectedListener true
}
}
false
}
private fun replaceFragment(fragment: Fragment) {
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.NavPageFragment, fragment)
fragmentTransaction.commit()
}
}
am I doing it correct? or am I supposed to call the categoryListView elsewhere? because I tried in my ListFragment which is where my activity code is and it gave a context error
here is how the Fragment activity looks:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/Bottom_List"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Fragments.ListFragment">
<ListView
android:id="#+id/categoryListView"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
You are trying to get the categoryListView resolved inside the activity but it is (as far as I understand) part of the fragment. This simply does not work.
The CategoryAdapter and the ListView should be used inside your HomeFragment class and this will work then.
So move this code into your fragment:
adapter = CategoryAdapter(this, DataService.categories)
categoryListView.adapter = adapter
Update
I would move it into the onViewCreated() method. There you can simply rely on the fact that there is a view available with a context.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adapter = CategoryAdapter(view.context, DataService.categories)
categoryListView.adapter = adapter // categoryListView will be not null here
}

MVVMCross Fragment and dynamically setting event commands

I have a MvxFragment that is bound to a view model that has two ICommand properties defined. This fragment contains an MvxListView and can be part of different activities/layouts dependant on the device size/orientation.
What I want to know is how to specify the ItemClick event command of the MvxBind property of the MvxListView dynamically or is there a better way to handle this use case? Should I use a separate fragment?
A similar use case to the one I am trying to achieve is within the Overview section of this Xamarin Dev Guide
View
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<MvxListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
local:MvxBind="ItemsSource Courses; ItemClick Comamnd1"
local:MvxItemTemplate="#layout/coursetemplate" />
</LinearLayout>
ViewModel (Simpified)
public class MyViewModel : MvxViewModel
{
private MvxCommand<MyMessage> messageCommand;
public MyViewModel (IMvxMessenger messenger, IHttpClientBuilderService httpClientBuilder)
{
this.httpClientBuilder = httpClientBuilder;
this.messenger = messenger;
}
public ICommand Comamnd1 {
get { return new MvxCommand<Course> ((c) => ShowViewModel<MyOtherViewModel>(c)); }
}
public ICommand Command2 {
get
{
messageCommand = messageCommand ?? new MvxCommand<MyMessage>(c =>
{
var message = new MyMessage(this, c);
messenger.Publish(message);
});
return selectedCourse;
}
}
}
OK, you can do this my overriding the OnActivityCreated event for the fragment as below.
public override void OnActivityCreated (Bundle savedInstanceState)
{
base.OnActivityCreated (savedInstanceState);
MvxListView list = Activity.FindViewById<MvxListView>(Resource.Id.[theID]);
if (Activity.FindViewById<View>(Resource.Id.[fragmentID]) != null)
list.ItemClick = ((MyViewModel)ViewModel).Command1;
else
list.ItemClick = ((MyViewModel)ViewModel).Command2;
}
You can pull out the list by using the Activity.FindViewById function and then set the appropraite ICommand or IMvxCommand from the ViewModel via the list.ItemClick event

Navigation Drawer opens to different fragments

I am a beginner when it comes to Android. I have spent hours reading tutorials, watching videos, and combing through stackoverflow trying to figure this out. Even with all the online resources available , I still can't figure this out.
I want my list view always on the main screen, but when I swipe I want the navigation drawer to slide in and display a different fragment depending on which item in the list view has been selected. For example if the user selected 'account', when they slide from the right the account fragment would display.
I've left in 'navList' and 'nav2List' for testing, but I would like these to be fragments.
XML
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
tools:context=".MainActivity">
<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/bankMenu"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true">
</ListView>
</RelativeLayout>
<!-- Side navigation drawer UI -->
<ListView
android:id="#+id/navList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="right"
android:background="#ffeeeeee"/>
<ListView
android:id="#+id/nav2List"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="right"
android:background="#ffeeeeee"/>
</android.support.v4.widget.DrawerLayout>
JAVA
public class MainActivity extends AppCompatActivity {
private ListView menu;
private ListView mDrawerList;
private ListView secondDrawerList;
private ArrayAdapter<String> mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
menu = (ListView) findViewById(R.id.bankMenu);
mDrawerList = (ListView)findViewById(R.id.navList);
secondDrawerList = (ListView)findViewById(R.id.nav2List);
addBankingListItems(); // populate the banking items list
menu.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
final int position, long id) {
// ListView Clicked item index
int itemPosition = position;
// ListView Clicked item value
String itemValue = (String) menu.getItemAtPosition(position);
// Show Alert
Toast.makeText(getApplicationContext(),
"Position :" + itemPosition + " ListItem : " + itemValue, Toast.LENGTH_LONG)
.show();
if (itemPosition < 4) { // TESTING FUNCTIONALITY
addDrawerItems();
} else {
addSecondDrawerItems();
}
}
});
}
---EDIT---
This issue has been resolved.
In the xml file, i changed the two ListViews to a RelativeView.
<RelativeLayout
android:id="#+id/frame_to_be_replaced"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="right"
android:layout_below="#+id/topRL"
android:background="#ffeeeeee">
</RelativeLayout>
In the MainActivity, I created a fragment manager, then after my switch statement, i replaced the frame with the required one. Hope this can help someone, send me a pm for more info.
FragmentManager fragmentManager = getFragmentManager();
switch (itemPosition) {
case 0:
fragment = MyFrag1.newInstance("", ""); // fragment declared above
break;
case 1:
fragment = MyFrag2.newInstance("", "");
break;
case 2: etc....
fragmentManager.beginTransaction()
.replace(R.id.frame_to_be_replaced, fragment)
.commit();

Getting multiple item with one touch in GridView

I have a gridview with custom buttons called bg_button in each cell. I am trying to create a boggle-like game and still a newbie in Android. I was searching through internet about this issue over a week now and still got nothing.
The issue is, when a touch_down I can get the specific item without any problem but when I start to move diagonal, I get multiple grid items that I do not want. For example;
A O F T
K T U L
T R S V
J O K U
The grid that I have above, when I touch T and then trying to move to O, I get;
T -> J -> O or T -> R -> O
I do not want J or R, but still I am touching that as well. I have tried to change to padding, or vertical and horizontal spacing but the issue remained the same. Could you please help me about this issue or at least can you give me a way to do this, or at least a specific tag that I can google and find information that can help me? Thank you so much for your time.
This is the part of my code for the touch event. I am saving the path to an ArrayList and I am sorry for the messy code. I will clean once I finish hardcoding:
final ArrayList<Integer> myList = new ArrayList<Integer>();
gridView = (GridView) this.findViewById(R.id.gridFriends);
MyAdapter gridAdapter = new MyAdapter(Boggler.this,board_1d);
gridView.setAdapter(gridAdapter);
gridView.setOnTouchListener(new View.OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
final GridView layout = (GridView)v;
int action = event.getActionMasked();
float currentXPosition = event.getX();
float currentYPosition = event.getY();
int position = gridView.pointToPosition((int) currentXPosition, (int) currentYPosition);
// position = layout.pointToPosition( (int)event.getX(), (int)event.getY() );
while(position == -1)
position=event.getAction();
View v2 =layout.getChildAt(position);
myList.add(position);
Bg_button bt = (Bg_button) v2.findViewById(R.id.grid_item);
bt.setPressed(true);
Log.d(this.getClass().getName(), String.format("Over view.id[%d]", position));
if (action == MotionEvent.ACTION_MOVE){
myList.add(position);
return true;
}
if (action == MotionEvent.ACTION_UP) {
Log.d(this.getClass().getName(), myList.toString());
int i=0,j=0;
int state = 0;
Object[] st = myList.toArray();
for (Object s : st) {
if (myList.indexOf(s) != myList.lastIndexOf(s)) {
myList.remove(myList.lastIndexOf(s));}
else {
v2 =layout.getChildAt(myList.get(myList.lastIndexOf(s)));
bt = (Bg_button) v2.findViewById(R.id.grid_item);
bt.setPressed(false);
name = name + bt.getText();
Log.d(this.getClass().getName(), name);
}
}
And this is the xml files that I am using button_boggler:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.example.proje_test.bg_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.example.proje_test.Bg_button
android:id="#+id/grid_item"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center"
android:clickable="false"
android:textAppearance="?android:attr/textAppearanceMedium"
android:background="#drawable/color_bg_selector"
android:textSize="50dp"
/>
</LinearLayout>
And activity_boggler:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ads="http://schemas.android.com/apk/lib/com.google.ads"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical" >
<GridView
android:id="#+id/gridFriends"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:clipChildren="true"
android:columnWidth="100dp"
android:gravity="center"
android:numColumns="4"
android:scrollbars="none"
android:stretchMode="columnWidth" >
</GridView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Large Text"
android:id="#+id/feedback"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />
</LinearLayout>
I have found a solution that is working for me. I have seen a couple of same questions about this kind of issues without answers because of that I will answer my own question so maybe it can help those who have this kind of issues. I made custom button class with different attrs called Bg_button:
public class Bg_button extends Button {
private static final int[] STATE_C = {R.attr.state_chosen};
private static final int[] STATE_R = {R.attr.state_right};
private static final int[] STATE_W = {R.attr.state_wrong};
public boolean mIschosen = false;
public boolean mIsright = false;
public boolean mIswrong = false;
public Bg_button(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 3);
if (mIschosen) {
mergeDrawableStates(drawableState, STATE_C);
}
if (mIsright) {
mergeDrawableStates(drawableState, STATE_R);
}
if (mIswrong) {
mergeDrawableStates(drawableState, STATE_W);
}
return drawableState;
}
public void setchosen(boolean ischosen) {mIschosen = ischosen;
refreshDrawableState();}
public void setright(boolean isright) {mIsright = isright;
refreshDrawableState();}
public boolean setwrong(boolean iswrong) {mIswrong = iswrong;
refreshDrawableState();
return true;}
#Override
public void getHitRect(Rect outRect) {
outRect.set(getLeft() + 20, getTop() + 20, getRight() - 20, getBottom() - 20);
}
}
So the solution I have found is limiting the touch area of the button so they do not intercept. I don't know how ethical this is but it is working for me now.
#Override
public void getHitRect(Rect outRect) {
outRect.set(getLeft() + 20, getTop() + 20, getRight() - 20, getBottom() - 20);
}
}
This is the best answer I can come up with so far. I limited the touch area of button to its center with reversing the transaction that we do for expanding it. And the 3 lines code above did the trick. I hope this helps.

Click on a specific part of an ImageView

I would like to be able to touch a specific part of an ImageView and display a toast.
Here is my xml file "dessintest.xml" :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="#+id/relative1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<ImageView
android:id="#+id/imageview1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="#drawable/dessin1"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
>
</ImageView>
<ImageButton
android:id="#+id/imageButton1"
android:background="#00000000"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:src="#drawable/image" />
</RelativeLayout>
Then here is my main file "DessinTest.java" :
public class DessinTest extends Activity {
ImageButton imageButton;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.dessintest);
addListenerOnButton();
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.dessin1);
TouchImageView touch = new TouchImageView(this);
touch.setImageBitmap(bm);
touch.setMaxZoom(4f); //change the max level of zoom, default is 3f
setContentView(touch);
}
public void addListenerOnButton() {
imageButton = (ImageButton) findViewById(R.id.imageButton1);
imageButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
Toast.makeText(DessinTest.this,"Click !", Toast.LENGTH_SHORT).show();
}
});
}
}
When I try that, I can zoom the ImageView but I cannot see the ImageButton.
When I delete this bit of code (the one for the zooming of my ImageView) :
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.dessin1);
TouchImageView touch = new TouchImageView(this);
touch.setImageBitmap(bm);
touch.setMaxZoom(4f); //change the max level of zoom, default is 3f
setContentView(touch);
it is working, I can see the ImageButton and click on it.
What I would like is to have both functionnalities, click and zoom.
Thanks !
OK I resolved my problem by using this sample :
https://github.com/chrisbanes/PhotoView/blob/master/sample/src/uk/co/senab/photoview/sample/SimpleSampleActivity.java
I can now zoom and touch/detect specific parts of the screen. It works very well !

Resources