Android ImageView bind srcCompact to a boolean Variable - android

I've just discovered how to bind variables to xml files, it's quite hard the first times but it really speed up the work.
How can I bind a variable to an imageView so that if the vabiable (boolean) goes from true to false the ImageView SrcCompact change as well?
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
app:srcCompat="#drawable/ic_bluetooth_indigo_a400_48dp"
android:id="#+id/imageView"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_margin="20dp" />
Here's my Imageview code with the BlueTooth Logo in Blue color.
I'd like to bind it to a "BluetoothEnable" Boolean and, in case "BluetoothEnable" goes "FALSE" i want the imageView to show a SrcCompact with the Bluetooth Logo in red.

I thought to share some solution if someone encounter this case.
When I tried to use the ternary expression, it didn't work for me:
<ImageView
...
app:srcCompat="#{isBluetoothEnabled? #drawable/ic_bluetooth_green : #drawable/ic_bluetooth_red}"
... />
So, I tried the binding adapter approach.
Here I created a binding adapter, that will receive a boolean value for isBluetoothEnabled then based on it choose the proper drawable image:
public class BindingAdapters {
#BindingAdapter("app:bluetoothBasedSrc")
public static void setSendState(ImageView v, boolean isBluetoothEnabled) {
int drawableRes = isBluetoothEnabled ? R.drawable.ic_bluetooth_green : R.drawable.ic_bluetooth_red;
v.setImageResource(drawableRes);
}
}
And here how we can use that binding adapter in the ImageView:
<ImageView
...
app:bluetoothBasedSrc=#{isBluetoothEnabled}
tools:srcCompat="#drawable/ic_bluetooth_green"
... />
Hope that solution helps this case.

Related

Android - Detecting when a view's constraints have been fully resolved/loaded

I have quite a complex problem to do with Android views. I am creating a paint application, and I have two views: a transparent background view and the pixel art board.
For both views, I want the height and width to be calculated off of the distance between view A and B:
Instead of calculating the distance between these two views, I simply 'constraint' a view in the middle like so, and then extract its height by using its measuredHeight property (and yes, you could also calculate the distance between view A and B in the code, but my problem still remains when I try that):
Now, here's the XML code:
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/fragment_background_color_daynight"
tools:context=".activities.canvas.CanvasActivity">
<View
android:id="#+id/activityCanvas_topView"
android:layout_width="match_parent"
android:layout_height="90dp"
android:background="#color/fragment_background_color_daynight"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.therealbluepandabear.pixapencil.customviews.colorswitcherview.ColorSwitcherView
android:id="#+id/activityCanvas_colorSwitcherView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:isPrimarySelected="false"
app:layout_constraintBottom_toBottomOf="#+id/activityCanvas_colorPickerRecyclerView"
app:layout_constraintEnd_toEndOf="#+id/activityCanvas_topView"
app:layout_constraintTop_toTopOf="#+id/activityCanvas_colorPickerRecyclerView"
app:primaryColor="#android:color/holo_green_dark"
app:secondaryColor="#color/black" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/activityCanvas_colorPickerRecyclerView"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="#+id/activityCanvas_topView"
app:layout_constraintEnd_toStartOf="#+id/activityCanvas_colorSwitcherView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/activityCanvas_primaryFragmentHost"
tools:listitem="#layout/color_picker_layout" />
<FrameLayout
android:id="#+id/activityCanvas_distanceContainer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/activityCanvas_tabLayout"
app:layout_constraintEnd_toEndOf="#+id/activityCanvas_primaryFragmentHost"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/activityCanvas_topView" />
<com.google.android.material.card.MaterialCardView
android:id="#+id/fragmentOuterCanvas_canvasFragmentHostCardViewParent"
style="#style/activityCanvas_canvasFragmentHostCardViewParent_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="1dp"
app:layout_constraintBottom_toTopOf="#+id/activityCanvas_tabLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/activityCanvas_topView">
<!-- At runtime, the width and height will be calculated -->
<com.therealbluepandabear.pixapencil.customviews.transparentbackgroundview.TransparentBackgroundView
android:id="#+id/activityCanvas_transparentBackgroundView"
android:layout_width="0dp"
android:layout_height="0dp" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.tabs.TabLayout
android:id="#+id/activityCanvas_tabLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:tabStripEnabled="false"
app:layout_constraintBottom_toTopOf="#+id/activityCanvas_viewPager2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_tools_str" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_filters_str" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_color_palettes_str" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_brushes_str" />
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/activityCanvas_viewPager2"
android:layout_width="0dp"
android:layout_height="110dp"
app:layout_constraintBottom_toBottomOf="#+id/activityCanvas_primaryFragmentHost"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="#+id/activityCanvas_coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<FrameLayout
android:id="#+id/activityCanvas_primaryFragmentHost"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Of course, when it comes to calculating, I thought it would be best practise to utilize AndroidX's OneShotPreDrawListener, like so:
OneShotPreDrawListener.add(binding.root) {
binding.activityCanvasTransparentBackgroundView!!.setViewWidth(binding.activityCanvasDistanceContainer!!.measuredHeight)
binding.activityCanvasTransparentBackgroundView!!.setViewHeight(binding.activityCanvasDistanceContainer!!.measuredHeight)
}
Now, for some reason, the result looks like so:
Why is this the case!
I did some debugging, and when I log the height of view C, I get the following:
This is wrong. So, as an experiment, I added a GlobalLayoutListener to detect when exactly the view's constraints get resolved:
binding.activityCanvasDistanceContainer?.viewTreeObserver?.addOnGlobalLayoutListener( object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
Log.d("M_LOG", binding.activityCanvasDistanceContainer?.measuredHeight.toString())
}
})
Result:
So, after the first couple of times it's 438, until after the 4th/5th time it shoots up to 1000.
I am really confused why this is happening, as I want to run the event when the constraints have been fully resolved and calculated, but using OneShotPreDrawListener (or any other alternative) is just running the event when the view has been drawn, but not yet when it has been positioned properly.
I am confused what to do. How can I run an event when the view's constraints have been fully calculated?
Edit for Chetichamp:
I have debugged this and I think I can reproduce this error and tell you in what scenario it occurs and in which scenario it does not.
Basically in my app, prior to activity creation, I have a fragment called NewProjectFragment, which looks like so:
Here's the code for when the 'Done' button is pressed:
binding.fragmentNewCanvasDoneButton.setOnClickListener {
checkForTitleError()
checkForWidthError()
checkForHeightError()
if (!invalidTitle && !invalidWidth && !invalidHeight) {
try {
val title =
binding.fragmentNewCanvasProjectTitleTextInputEditText.text.toString()
val widthValue: Int =
binding.fragmentNewCanvasWidthTextInputEditText.text.toString().toInt()
val heightValue: Int =
binding.fragmentNewCanvasHeightTextInputEditText.text.toString().toInt()
if (widthValue + heightValue >= 2000 && (requireActivity() as MainActivity).showLargeCanvasSizeWarning) {
val frameLayout: FrameLayout =
this#setOnClickListeners.activity?.layoutInflater?.inflate(
R.layout.dont_show_large_canvas_warning_again_checkbox,
requireView().findViewById(android.R.id.content),
false
)
as FrameLayout
val checkBox = frameLayout.getChildAt(0) as MaterialCheckBox
requireActivity().showDialog(
getString(R.string.generic_warning_in_code_str),
getString(R.string.dialog_large_canvas_warning_text_in_code_str),
getString(R.string.dialog_large_canvas_warning_positive_button_text_in_code_str),
{ _, _ ->
if (checkBox.isChecked) {
(requireActivity() as MainActivity).showLargeCanvasSizeWarning =
false
with((requireActivity() as MainActivity).sharedPreferenceObject.edit()) {
putBoolean(
StringConstants.Identifiers.SHARED_PREFERENCE_SHOW_LARGE_CANVAS_SIZE_WARNING_IDENTIFIER,
(requireActivity() as MainActivity).showLargeCanvasSizeWarning
)
apply()
}
}
caller.onDoneButtonPressed(
title,
widthValue,
heightValue,
paramSpotLightInProgress
)
},
getString(R.string.dialog_unsaved_changes_negative_button_text_in_code_str),
{ _, _ ->
},
frameLayout
)
} else {
caller.onDoneButtonPressed(
title,
widthValue,
heightValue,
paramSpotLightInProgress
)
}
} catch (exception: Exception) {
HapticFeedbackWrapper.performHapticFeedback(binding.fragmentNewCanvasDoneButton)
}
} else {
HapticFeedbackWrapper.performHapticFeedback(binding.fragmentNewCanvasDoneButton)
}
}
As you can see, it has a listener, so the code for the listener, which is in MainActivity, is like so (maybe this is causing the issue? and I just don't need a listener like this? I don't know if you think this is why I get the issue):
fun MainActivity.extendedOnDoneButtonPressed(projectTitle: String, width: Int, height: Int, spotLightInProgress: Boolean) {
startActivity(
Intent(this, CanvasActivity::class.java)
.putExtra(StringConstants.Extras.PROJECT_TITLE_EXTRA, projectTitle)
.putExtra(StringConstants.Extras.WIDTH_EXTRA, width)
.putExtra(StringConstants.Extras.HEIGHT_EXTRA, height)
.putExtra(StringConstants.Extras.SPOTLIGHT_IN_PROGRESS_EXTRA, spotLightInProgress)
)
}
Now, what I come to the conclusion is that all of this extra work is causing a delay, because when you simply tap on a pre-existing project, we can see that the intent is much simpler:
fun MainActivity.extendedOnCreationTapped(param: PixelArt) {
startActivity(
Intent(this, CanvasActivity::class.java)
.putExtra(StringConstants.Extras.INDEX_EXTRA, pixelArtData.indexOf(param))
.putExtra(StringConstants.Extras.PROJECT_TITLE_EXTRA, param.title))
}
With a simple intent like so, the problem doesn't get reproduced, and it sizes properly.
What I relaized is that the work done in NewProject fragment is causing a delay, and when I simply scrap out the work and perfom a simple intent, the problem is 'fixed'. I don't know how to fix this but hopefully it can help with finding a solution.
Debugging even further
When I debug the issue even further, I notice something strange. The measuredHeight of the root layout jumps up by one thousand:
This is not observed when the creation is tapped with the simple intent. I am lost for words as to how strange this bug is, I've never seen this in my life.
There is something going on with your code that is touching your views that is causing the multiple layouts. Without that code being posted, it is impossible to say what.
To help you with debugging, set the one shot to the following:
OneShotPreDrawListener.add(binding.root) {
Log.d("Applog", "OneShot")
binding.activityCanvasTransparentBackgroundView.layoutParams.width =
binding.activityCanvasDistanceContainer.measuredWidth
binding.activityCanvasTransparentBackgroundView.layoutParams.height =
binding.activityCanvasDistanceContainer.measuredHeight
binding.activityCanvasTransparentBackgroundView.requestLayout()
}
In my test, when placing this in the onCreateView() function of a fragment, "OneShot" is logged just once and the size of the view is adjusted as you say it should be. (btw, requestLayout() was required in this test.)
I would take a look at any other code that touches your views to see if something else is triggering the multiple calls: maybe setWidth() or setHeight().
I will add that I have noticed in the past that ConstraintLayout will call the global layout listener multiple times. Usually, I would say, the listener is removed on the first call, so this behavior would not be noticed.
Instead of the global layout listener or the predraw listener, you could try a layout listener:
binding.root.doOnLayout {
// Your code here
}
or
binding.root.doOnNextLayout {
// Your code here
}
doOnNextLayout is guaranteed to be called only once, while doOnLayout may be called multiple times.
Wow guys.
I finally found a solution, after 12 hours of nonstop debugging, almost going clinically insane, and logging and destroying my codebase.
I realized that the keyboard from the NewProjectFragment was cutting the view, causing the bug. Holy moly... I am ecstatic that I finally found why this is happening but at the same time shocked how I didn't discover this before!
The solution was to add the following to the activity:
<activity
android:name="com.therealbluepandabear.pixapencil.activities.canvas.CanvasActivity"
android:windowSoftInputMode="stateHidden" // this
android:hardwareAccelerated="true"
android:exported="false" />
Prior to this, the windowSoftInputMode was adjustResize -- causing the bug.

How can I set the onTouchListener directly on the xml layout file using data binding?

I have been successful with adding onClick listeners directly
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{action::onItemClicked}" />
and onRefreshListener listeners as well
<android.support.v4.widget.SwipeRefreshLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:onRefreshListener="#{action::onRefresh}">
but I cannot understand why I am not able to do the following
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:onTouchListener="#{action::onItemTouched}" />
It shows the following error:
Listener class android.view.View.OnTouchListener with method onTouch did not match signature of any method action::onItemTouched
And another error if I use android: instead of app: or onTouch instead of onTouchListener.
Yet the method signature of onItemTouched is as defined in the View.java source file:
public void onItemTouched(View v, MotionEvent event) {
// no dice
}
As far as I can see, TextView is a View and as such it should have worked: https://android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/android/view/View.java#14494
So what am I doing wrong, why won't this work? Should it be a different attribute name?
And please no #BindingAdapter or similar suggestions, I already know how to do it that way. My objective is to keep the model clutter free and attach the methods directly on the views as I did for clicks and swipe refresh.
Got it working, the only problem was the method was void, but the onTouch signature requires a boolean return. In the end this is how it should be:
Layout file sample
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onTouch="#{action::onItemTouched}" />
or
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onTouchListener="#{action::onItemTouched}" />
Action class method
public boolean onItemTouched(View v, MotionEvent event) {
// now it works, do your magic
return true; // or return false, depending on what you want to do
}
Only worry now is Android Studio complains that android:onTouch is an unknown attribute, I hope it won't turn non-public for some odd reason in the future. For that reason I will be using the following method in the layout just to get rid of the warning message
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:onTouch="#{action::onItemTouched}" />
// app:onTouchListener works as well

Setting textview visibility inside retrofit on failure callback

I am trying to set the visibility of a Textview depending on the response returned by Retrofit . onFailure, I am setting the visibility to visible tv_no_cat.setVisibility(View.VISIBLE); However, this does not work. I am doing the same with a ContentLoadingProgressBar and it's working appropriately. Here is my code
public void onFailure(Call<List<MoviesCategory>> call, Throwable t) {
tv_no_cat.setVisibility(View.VISIBLE); ////This does not work
Boolean x = tv_no_cat.getVisibility() == View.VISIBLE;
Toast.makeText(getContext(), "Network Error "+x, Toast.LENGTH_LONG).show(); //This shows true
tv_no_cat.setTextColor(Color.BLUE);
videoLoadingPb.setVisibility(View.GONE); //This works
}
My guess is that am trying to set the textview which is on UI thread from another thread. If so, why is it working for the ContentLoadingProgressBar?
Here is my xml for the 2 views
<android.support.v4.widget.ContentLoadingProgressBar
android:id="#+id/loading_categories"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_gravity="center"
android:visibility="visible" />
<TextView
android:layout_below="#id/loading_categories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="No Movie Category Found"
android:id="#+id/tv_no_cat"
android:textAlignment="center"
android:layout_gravity="bottom"
android:visibility="gone"
/>
Any help on how I can set the textview Visibility to VISIBLE is welcome.
From the docs:
On Android, callbacks will be executed on the main thread...
So this is not your issue, and I'm doubtful that it's related to Retrofit, especially when it works for another view. Please make sure that there are no problem in your xml design, and that there are no other methods that affect this view's visibility.
are registering Textview inside Oncreate or some where else ?
And this code you are using inside activity of fragment ?
Please check this ...
tv_no_cat=(TextView)findViewById(R.id.tv_no_cat);(Inside Activity)
tv_no_cat=(TextView)rootView.findViewById(R.id.tv_no_cat);(Inside Fragment )

ImageButton stretch src to fill

This question comes up quite often, however in all examples i have found, the image src is defined in the XML. e.g android:src="..."
My code doesn't specificy the src untill the activity, using ImageButton.setImageResource() as it is a single button, performing play/stop
How do i fill the ImageButton with the src, when src is defined later?
I tryed ImageButton.setScaleType="fitXY", however sdk doesn't like it using string...
UPDATE: After trying to use the suggested below, the problem still occurs. here is more explanation to help
<ImageButton
android:id="#+id/imgStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
/>
public class MainActivity extends Activity{
private ImageButton player;
protected void onCreate(){
player = (ImageButton) findViewById(R.id.imgStart);
...
if(isPlaying){
player.setImageResource(bitmap image);
}
else{
player.setImageResource(different bitmap image);
}
...
}
Use ScaleType.CENTER_CROP instead
player .setScaleType(ImageView.ScaleType.CENTER_CROP);
It seems CENTER_CROP works as fit_xy.
but, it's not sure why it does..
Please try below code to set ScaleType programatically
ImageButton object".setScaleType(ScaleType.FIT_XY)
"ImageButton object" should be object of class ImageButton
(that you defined probably in xml).
Do it this way
ImageButton object.setScaleType(ScaleType.FIT_XY)
Use ScaleType.FIT_XY instead of fitXY in your code
ImageButton.setScaleType="fitXY"
yes this will not work, because you have to use it properly using below code also should setAdjustViewBounds to true.like this:
player.setAdjustViewBounds(true);
player.setScaleType(ScaleType.FIT_XY);
This will definitely work!

Clickable image - android

How do i make a image clickable? I have tried some ways, but without success.
Here's the last code i tried (it's clickable but gets error):
ImageView btnNew = (ImageView) findViewById(R.id.newbutton);
btnNew.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// do stuff
}
});
and here's the part from xml:
<ImageView
android:src="#drawable/tbnewbutton"
android:text="#string/hello"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:id="#+id/newbutton"
android:clickable="true"
android:onClick="clickImage"
android:layout_height="wrap_content"
android:layout_alignParentTop="true" />
When running this code, and clicking the image i get this error:
01-24 19:14:09.534: ERROR/AndroidRuntime(1461): java.lang.IllegalStateException: Could not find a method clickImage(View) in the activity
HERE'S THE SOLUTION:
The XML:
<ImageButton
android:src="#drawable/tbnewbutton"
android:text="#string/hello"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:id="#+id/newbutton"
android:clickable="true"
android:onClick="clickNew"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="#null" />
The code :
public void clickNew(View v)
{
Toast.makeText(this, "Show some text on the screen.", Toast.LENGTH_LONG).show();
}
As other said: make this an ImageButton and define its onClick attribute
<ImageButton
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="left"
android:onClick="scrollToTop"
android:src="#drawable/to_top_button"
/>
The image is here encoded in a file res/drawable/to_top_button.png. If the user clicks on the button, the method scrollToTop() is called. This method needs to be declared in the class that sets the Layout with the ImageButton as its content layout.
public void scrollToTop(View v) {
...
}
Defining the OnClick handler this way saves you a lot of typing and also prevents the need to anonymous inner classes, which is beneficial for the memory footprint.
Does an ImageButton do what you want?
The error message you get implies that you do not have a method in your activity that matches your onClick handler.
You should have something like clickImage(View view) in your activity with the click handling implementation.
You could just use the ImageButton class...
http://developer.android.com/reference/android/widget/ImageButton.html
Use a ImageButton ;)
You've set the onclick method to call "clickImage" when the image is clicked in your XML, but you haven't created a clickImage method in your code. You shouldn't need to set the onclick listener at all. Just implement the method from your XML and you should be set.

Categories

Resources