Putting labels on top of buttons in an Android app - android

I am trying to write a first Android app and I hit the following issue.
This is a loop handling some buttons:
for (i in 0..7) {
val btnID = resources.getIdentifier('N'.plus(i.toString()),"id",packageName)
val imageBtn = findViewById<ImageButton>(btnID)
imageBtn.setBackgroundColor(0x00)
imageBtn.setOnClickListener {
val result = Math.pow(2.toDouble(),i.toDouble()).toInt()
val textView = findViewById<TextView>(R.id.textView2).apply {//
text = result.toString()
}
}
// Here I want to put a sticker: "Hi" on top of the button (imageBtn).
.....
}
The code above works, and the buttons behave as I expect.
Now I would like to stick a label on top of each button.
How can I do that? I have already tried tens of ways, following sample code I found on the net, but nothing works.
Below is a graphic to illustrate what I mean more precisely.
Of course "Hi" cannot be part of the button image because I need to change it dynamically. It can later become "Ho", "He", "Pa", ... or whatever according to the state of the app.

Hope this might be work
for (i in 0..7) {
val btnID = resources.getIdentifier('N'.plus(i.toString()),"id",packageName)
val imageBtn = findViewById<ImageButton>(btnID)
imageBtn.setBackgroundColor(0x00)
val result = Math.pow(2.toDouble(),i.toDouble()).toInt()
imageBtn.setOnClickListener {
val textView = findViewById<TextView>(R.id.textView2).apply {//
text = result.toString()
}
}
// Here I want to put a sticker: "Hi" on top of the button (imageBtn).
imageBtn.text = result.toString()
}

Use this to your layout.
<RelativeLayout
android:id="#+id/layoutButton"
android:layout_width="60dp"
android:layout_height="60dp">
<ImageButton
android:id="#+id/imgBtn"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hi"
android:layout_centerInParent="true"
android:textSize="20sp" />
</RelativeLayout>
And Give background as per you want to ImageButton.

For Constraintlayout Use this.
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/layoutButton"
android:layout_width="60dp"
android:layout_height="60dp">
<ImageButton
android:id="#+id/imgBtn"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hi"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="#+id/imgBtn"
app:layout_constraintEnd_toEndOf="#+id/imgBtn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/imgBtn" />
</androidx.constraintlayout.widget.ConstraintLayout>

You can use simlpe Button widget instead of ImageButton, it has text propertie. To make the button round just set simple shape drawable to the background.
For example, create drawable circle.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#android:color/darker_gray"/>
</shape>
And use it in the Button widget:
<Button
...
android:background="#drawable/circle"
.... />

Since I had to spend some time and this may well be useful to someone else, I put here my solution. It now works exactly as I want.
Here it is:
for (i in 0..7) {
val btnID = resources.getIdentifier('N'.plus(i.toString()),"id",packageName)
val imageBtn = findViewById<ImageButton>(btnID)
imageBtn.setBackgroundColor(0x00)
imageBtn.setOnClickListener {
val result = Math.pow(2.toDouble(),i.toDouble()).toInt()
val textView = findViewById<TextView>(R.id.textView2).apply {//
text = result.toString()
}
}
setSticker(i,btnID)
}
fun setSticker(n:Int,btn:Int) {
val label = TextView(this)
label.id = View.generateViewId()
label.text = "Hi"
label.setTextColor(Color.rgb(0xFF,0xFF,0xFF))
label.setTypeface(null, Typeface.BOLD)
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, 17.dpToPixels(this))
label.elevation = 0.dpToPixels(this) // To make the label visible (i.e. on top)
constraintLayout?.addView(label)
val constrSet = ConstraintSet()
constrSet.clone(constraintLayout)
constrSet.connect(label.id, ConstraintSet.LEFT, btn, ConstraintSet.LEFT)
constrSet.connect(label.id, ConstraintSet.RIGHT, btn, ConstraintSet.RIGHT)
constrSet.connect(label.id, ConstraintSet.TOP, btn, ConstraintSet.TOP)
constrSet.connect(label.id, ConstraintSet.BOTTOM, btn, ConstraintSet.BOTTOM)
constrSet.applyTo(constraintLayout)
}

Related

Having an imageview right aligned to a textview

Looking for a way to align an Imageview which would be in line with the textview and have it be able to adjust if the textview is too long and will extend to the next line. The reason it is an imageView, is I want it to be clickable
I haven't been successful, I have tried image and text spans and also constraintlayout but I can't seem to the get following result below:
Thanks
[1]: https://i.stack.imgur.com/onPT9.png
Here is a way to add an image to the end of the text in a TextView whether the text spans one or several lines. The approach is to add a space to the end of each text string and replace that space with an ImageSpan overlaid with a ClickableSpan.
Here is the layout used:
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="#android:color/holo_blue_light"
android:padding="8dp"
android:text="#string/test_string_1"
android:textSize="28sp" />
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="#android:color/holo_blue_light"
android:padding="8dp"
android:text="#string/test_string_2"
android:textSize="28sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
And the string resources:
<string name="test_string_1"><b>This</b> is a short string.</string>
<string name="test_string_2"><b>This</b> is some text that spans several lines and is just used as an example.</string>
After waiting for the layout to complete, we can add the images to the end of the text for each TextView.
binding.root.doOnNextLayout {
// Make the drawables truly clickable.
binding.textView1.text = addEndImage(binding.textView1)
binding.textView1.movementMethod = LinkMovementMethod.getInstance()
binding.textView2.text = addEndImage(binding.textView2)
binding.textView2.movementMethod = LinkMovementMethod.getInstance()
}
private fun addEndImage(textView: TextView): Spannable {
// Get out (probable) StaticLayout from the TextView and some of its attributes.
val size = textView.layout.run {
val lastLine = lineCount - 1
-getLineAscent(lastLine) * 2 / 3
}
// Get the text and add a space for the spans at the end. If we are certain that the
// text can be accurately represented by an unspanned String, we could just use
// "${binding.textView.text} ".toSpannable()
val text = SpannableStringBuilder(textView.text).append(" ")
// Get the drawable and size it to fit on our last line.
val d = AppCompatResources.getDrawable(requireContext(), R.drawable.circle)!!
d.setBounds(0, 0, size, size)
// Set the ImageSpan to replace the space we added at the end. Vertical positioning
// and the size of the image may need to be tweaked.
val span = ImageSpan(d, ImageSpan.ALIGN_BASELINE)
text.setSpan(span, text.length - 1, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
// Set the ClickableSpan to overlay the ImageSpan we added at the end.
val clickableSpan = MyClickableSpan()
text.setSpan(
clickableSpan,
text.length - 1,
text.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
return text
}
class MyClickableSpan : ClickableSpan() {
override fun onClick(widget: View) {
Toast.makeText(widget.context, "Clicked", Toast.LENGTH_SHORT).show()
}
}
If you need to use an ImageView for accessibility or other reasons, you can do that as follows.
The layout:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/holo_blue_light"
android:padding="8dp"
android:text="#string/test_string_2"
android:textSize="28sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="#+id/imageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="8dp"
android:src="#drawable/circle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Again, after layout is complete, we can do the following that will give the ImageView top and left margins that will place it at the end of the text.
binding.root.doOnNextLayout {
val imageView = binding.imageView
imageView.setOnClickListener() {
Toast.makeText(requireContext(), "Clicked", Toast.LENGTH_SHORT).show()
}
val textView = binding.textView2
val layout = textView.layout
val imageY = textView.bottom
val shiftX = layout.getLineRight(layout.lineCount - 1)
val shiftY =
-(imageView.y - imageY) - imageView.paddingTop - textView.height + layout.getLineBaseline(
layout.lineCount - 1
) - imageView.height / 2
val lp = (imageView.layoutParams as ViewGroup.MarginLayoutParams)
lp.marginStart = shiftX.toInt()
lp.topMargin = shiftY.toInt()
imageView.layoutParams = lp
}
You will have to work with the exact size and placement, but this is a technique that will work.

Is it possible to add a footer to the MaterialDateRange Picker?

I'm pretty sure this is impossible, but I just wanted to check before I use a 3rd party library. The DatePicker doesn't really let you customize the UI, and it's basically a full screen DialogFragment so putting something above or below (or even on top) doesn't seem to be an option.
My calendar currently looks like this:
But I would like it to look like this design doc with a footer at the bottom. Let me know if you have any ideas, because I'm out of them. Thanks!
val builder = MaterialDatePicker.Builder.dateRangePicker()
.setTheme(R.style.ThemeOverlay_MaterialComponents_MaterialCalendar_Fullscreen)
.setCalendarConstraints(
CalendarConstraints.Builder()
.setStart(Date().time)
.setEnd(oneYearFrom().time)
.setValidator(AvailabilityValidator(it.unavailableDays)).build()
)
builder.setInputMode(MaterialDatePicker.INPUT_MODE_CALENDAR)
builder.build().show(requireActivity().supportFragmentManager, TAG_DATE_RANGE_PICKER)
The only way to add a footer to the MaterialDateRangePicker is to get access to the Picker root View and from there you can find the Picker FrameLayout Container (with Id app:id/mtrl_calendar_frame) and get access to its child View the Vertical LinearLayout Container which contains of three children: (a Days-GridView, a Separator-View and a RecyclerView) in a vertical orientation. What you want is to add a footer below the RecyclerView. Because the parent is a LinearLayout you can add the weight attribute to all its children to be weight=0 except the RecyclerView (which render the months) to be weight=1 to get all the remaining space.
To access the Picker root View you have to observe the MaterialDatePicker (DialogFragment) lifecycle and add the footer View in onStart() of DialogFragment. For this purpose you can use the DefaultLifecycleObserver.
Below is full working example to add a footer View:
private fun showRangeDatePicker() {
//initialize dateRangePicker
val builder = MaterialDatePicker.Builder.dateRangePicker()
builder.setTheme(R.style.ThemeOverlay_MaterialComponents_MaterialCalendar_Fullscreen)
//set CalendarConstraints
val constraintsBuilder = CalendarConstraints.Builder()
val c: Calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
constraintsBuilder.setStart(c.getTimeInMillis())
c.add(Calendar.YEAR, 1)
constraintsBuilder.setEnd(c.getTimeInMillis())
builder.setCalendarConstraints(constraintsBuilder.build())
//set InputMode and Title
builder.setInputMode(MaterialDatePicker.INPUT_MODE_CALENDAR)
builder.setTitleText("Pick dates")
//get MaterialDatePicker instance and set a lifecycle Observer to listen for DialogFragment lifecycle
//DefaultLifecycleObserver needs androidx.appcompat:appcompat:1.4.0 and add the FooterView in onStart of DialogFragment
val picker: MaterialDatePicker<*> = builder.build()
picker.lifecycle.addObserver(object : DefaultLifecycleObserver
{
override fun onCreate(owner: LifecycleOwner) {
}
override fun onStart(owner: LifecycleOwner) {
//add footerView in onStart of DialogFragment so you can access the picker.view
addMyFooterView(picker)
}
override fun onResume(owner: LifecycleOwner) {
}
override fun onDestroy(owner: LifecycleOwner) {
//remove Lifecycle Observer
picker.lifecycle.removeObserver(this)
}
})
picker.show(supportFragmentManager, picker.toString())
}
private fun addMyFooterView(picker: MaterialDatePicker<*>) {
//get the MaterialDatePicker root View
val rootView = picker.view as LinearLayout
//find the Picker FrameLayout Container with ID: app:id/mtrl_calendar_frame
val frameLayout = rootView.findViewById<FrameLayout>(resources.getIdentifier("mtrl_calendar_frame", "id", packageName))
//and get the Picker LinearLayout Container (which contains of 3 children: Days-GridView, Separator-View and a RecyclerView)
val linearContainer = frameLayout.getChildAt(0) as LinearLayout
//for each child in Vertical LinearLayout Container set the weight to RecyclerView to 1 and 0 to all others.
//(This is necessary before adding the footerView to distribute correctly each children Height)
for (i in 0 until linearContainer.childCount) {
val v = linearContainer.getChildAt(i)
val params = v.layoutParams as LinearLayout.LayoutParams
params.weight = if (v is RecyclerView) 1f else 0f
v.layoutParams = params
}
//and finally add the 4th child in the above linearContainer to be the FooterView
val myFooterView = layoutInflater.inflate(R.layout.footer_layout, null)
val myFooterViewHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics).toInt()
myFooterView.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, myFooterViewHeight, 0f)
myFooterView.setBackgroundColor(Color.WHITE)
linearContainer.addView(myFooterView)
//set any FooterView click listeners
myFooterView.findViewById<View>(R.id.textView2).setOnClickListener {}
}
Where footer_layout is your custom footer xml layout like below:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp">
<View
android:id="#+id/separatorView"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentTop="true"
android:background="#android:color/darker_gray"/>
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:textStyle="bold"
android:layout_marginEnd="10dp"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:text="Match exact dates" />
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:textStyle="normal"
android:layout_marginEnd="10dp"
android:layout_centerVertical="true"
android:layout_toEndOf="#+id/textView1"
android:text="±1 day" />
<TextView
android:id="#+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:textStyle="normal"
android:layout_marginEnd="10dp"
android:layout_centerVertical="true"
android:layout_toEndOf="#+id/textView2"
android:text="±3 days" />
</RelativeLayout>
Result:

How can I make Android linearlayout refresh after addView some textview?

My res xml has a linearlayout and a button
<LinearLayout
android:id="#+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:gravity="center_horizontal"
android:orientation="horizontal"
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="TextView" />
</LinearLayout>
<Button
android:id="#+id/btn_add_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="84dp"
android:text="Button" />
click the button
some char array added to linearLayout one by one
val chars = "Hello".toCharArray()
btn_add_text.setOnClickListener {
linearLayout.removeAllViews()
chars.forEachIndexed { index, char ->
val tv = Textview(this)
tv.textSize = 36f
tv.text = char
tv.id = index
linearLayout.addView(tv)
linearLayout.invalidate()
}
After forEachIndexed loop has finished linearLayout refreshed and can see [H][e][l][l][o] five textviews.
But I want to make linearLayout refresh after each linearLayout.addView(tv).
As far as I know if you want a view to redraw you call invalidate and if you want to update the viewbounds you need to call requestLayout as well.
If you want to see step by step you can try this:
val handler = Handler()
btn_add_text.setOnClickListener {
linearLayout.removeAllViews()
chars.forEachIndexed { index, char ->
val tv = TextView(context!!)
tv.textSize = 24f
tv.text = char.toString()
tv.id = index
handler.postDelayed(Runnable {
linearLayout.addView(tv)
},500 * index.toLong())
}
}
I think linearlayout is refreshing so fast, you are not able to see intermediate refreshes, what you can do is, use a worker thread and make it sleep for 500 ms between each iteration, and post data to main thread via handler, your each change of charachter will be visible.

onBindViewHolder binding view inconsistently

I have a ViewHolder that is meant to appear differently depending on whether it is on the left or right side of a two-column RecyclerView with a GridLayoutManager. Note the connector lines on either side of the view:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_marginTop="12px"
android:layout_marginBottom="12px"
android:layout_gravity="center"
android:gravity="center_vertical"
android:layout_height="wrap_content"
android:id="#+id/citation_select_holder">
<ImageView
android:src="#drawable/connector_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="#+id/citation_select_connector_right"/>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="348px"
android:layout_height="104px"
android:layout_weight="1"
android:background="#drawable/button_background_white"
android:id="#+id/citation_select_citation_holder">
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_marginLeft="28px"
>
<TextView
tools:text="123456"
android:textAppearance="#style/citation_select_item_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/citation_select_citation_number_text"/>
<TextView
tools:text="Pay by: Nov 18th, 2019"
android:textAppearance="#style/citation_select_item_due_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/citation_select_due_date_text"/>
<TextView
tools:text="a category label"
android:textAppearance="#style/citation_select_item_category"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/citation_select_category_text"/>
</LinearLayout>
<TextView
tools:text="$10.00"
android:textAppearance="#style/citation_select_item_cost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:id="#+id/citation_select_cost_text" android:layout_marginRight="28px"/>
</RelativeLayout>
<ImageView
android:src="#drawable/connector_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="#+id/citation_select_connector_left"/>
</LinearLayout>
The connector line far from the side that the view appears on is meant to disappear when onBindViewHolder is called, and the margins updated accordingly.
if (position % 2 == 0) {
holder.itemView.findViewById<ImageView>(R.id.citation_select_connector_left).visibility = View.GONE
val marginLayoutParams1 = holder.citationHolder.layoutParams as GridLayoutManager.LayoutParams
marginLayoutParams1.setMargins(0, 12, 12, 12)
holder.itemView.findViewById<LinearLayout>(R.id.citation_select_holder).layoutParams =
marginLayoutParams1
} else {
holder.itemView.findViewById<ImageView>(R.id.citation_select_connector_right).visibility = View.GONE
val marginLayoutParams2 = holder.citationHolder.layoutParams as GridLayoutManager.LayoutParams
marginLayoutParams2.setMargins(12, 12, 0, 12)
holder.itemView.findViewById<LinearLayout>(R.id.citation_select_holder).layoutParams =
marginLayoutParams2
}
Scrolling is done exclusively via on-screen buttons in increments of six. The first two pages load normally:
But the pattern begins to break down at citation #14. Keep in mind that the citation numbers correspond are the view's position within the RecyclerView:
What is happening to change the behavior?
I think I know what can help you fix this. I presume that it is reusing the old view, as a RecyclerView should and nowhere in your code is there a line to set the visibility of the connector lines back to visible.
You should add to both your GONE visibilities also the code to set the other to visible:
if (position % 2 == 0) {
holder.itemView.findViewById<ImageView>(R.id.citation_select_connector_right).visibility = View.VISIBLE
holder.itemView.findViewById<ImageView>(R.id.citation_select_connector_left).visibility = View.GONE
val marginLayoutParams1 = holder.citationHolder.layoutParams as GridLayoutManager.LayoutParams
marginLayoutParams1.setMargins(0, 12, 12, 12)
holder.itemView.findViewById<LinearLayout>(R.id.citation_select_holder).layoutParams =
marginLayoutParams1
} else {
holder.itemView.findViewById<ImageView>(R.id.citation_select_connector_left).visibility = View.VISIBLE
holder.itemView.findViewById<ImageView>(R.id.citation_select_connector_right).visibility = View.GONE
val marginLayoutParams2 = holder.citationHolder.layoutParams as GridLayoutManager.LayoutParams
marginLayoutParams2.setMargins(12, 12, 0, 12)
holder.itemView.findViewById<LinearLayout>(R.id.citation_select_holder).layoutParams =
marginLayoutParams2
}
Additional explanation
The RecyclerView will reuse some old view, right? Well since both lines are VISIBLE at the start, you assume that's their default state. But when you set the lines to GONE you never put them back to visible, and thus if RecyclerView reuses that view, it'll not add the margin there and it will just be missing the connector line. You always want to have EVERY line of code in onBindViewHolder to have a matching line that reverts it.
Vucko's answer is good, and the overall point (always update every component of your viewholder) is something you should absolutely do.
I wanted to add, however, that it appears as though you are not following the ViewHolder pattern correctly: your onBindViewHolder() method should never call findViewById(). Instead, your ViewHolder class should find each view once and then save references to them.
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val connectorRight: ImageView = itemView.findViewById(R.id.citation_select_connector_right)
val connectorLeft: ImageView = itemView.findViewById(R.id.citation_select_connector_left)
// ...
}
And then you can use these fields directly inside onBindViewHolder():
if (position % 2 == 0) {
holder.connectorRight.visibility = View.VISIBLE
holder.connectorLeft.visibility = View.GONE
// ...
} else {
holder.connectorLeft.visibility = View.VISIBLE
holder.connectorRight.visibility = View.GONE
// ...
}

TextClock in AppWidget not responding to any function calls

I'm working on an information widget for the LG V20/V10, to run in the second screen (very easy to add one: just set your category to 36864/0x9000). Currently, I have a battery view and a TextClock in the RemoteViews layout:
<!--info_widget.xml-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/main"
android:layout_width="1040px"
android:layout_height="160px">
<include
android:id="#+id/battery"
layout="#layout/battery_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_toLeftOf="#+id/clock"
android:layout_gravity="right" />
<include
android:id="#+id/clock"
layout="#layout/clock_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_gravity="right" />
</RelativeLayout>
<!--battery_view.xml-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="160px"
android:layout_gravity="center"
android:foregroundGravity="center"
android:orientation="vertical"
android:padding="2dp">
<ImageView
android:id="#+id/battery_view"
android:layout_width="70px"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_weight="1"
android:scaleType="centerCrop"
android:tint="#fff"
app:srcCompat="#drawable/ic_battery_alert_black_24dp" />
<TextView
android:id="#+id/battery_percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:textColor="#fff"
android:textSize="40px" />
</LinearLayout>
<!--clock_view.xml-->
<TextClock xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="2dp"
android:textSize="64px"
android:id="#+id/clock_view"/>
In my code, I have these functions, which get called from onUpdate() for every widget ID (in Kotlin):
private fun updateBattery(views: RemoteViews) {
val level = mBatteryManager?.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
val charging = mBatteryManager?.isCharging
mBatteryState.updateState(level as Int, charging as Boolean)
views.setImageViewResource(R.id.battery_view, mBatteryState.imageResource)
var color = mPrefs?.getInt("battery_color", Color.WHITE)
var showPercent = mPrefs?.getBoolean("show_percent", true)
if (color == null) color = Color.WHITE
if (showPercent == null) showPercent = true
views.setInt(R.id.battery_view, "setColorFilter", color)
views.setTextColor(R.id.battery_percent, color)
if (showPercent) {
views.setViewVisibility(R.id.battery_percent, View.VISIBLE)
views.setTextViewText(R.id.battery_percent, mBatteryState.percent.toString() + "%")
} else {
views.setViewVisibility(R.id.battery_percent, View.GONE)
}
}
private fun updateClock(views: RemoteViews) {
var hour_24 = mPrefs?.getBoolean("24_hour", false)
var amPm = mPrefs?.getBoolean("am_pm", true)
var showDate = mPrefs?.getBoolean("show_date", false)
var color = mPrefs?.getInt("clock_color", Color.WHITE)
if (hour_24 == null) hour_24 = false
if (amPm == null) amPm = true
if (showDate == null) showDate = false
if (color == null) color = Color.WHITE
val format: CharSequence = if (showDate) "EE, d " else {""} + if (hour_24) "k" else {"h"} + ":mm" + if (amPm) " a" else {""}
views.setCharSequence(R.id.clock_view, "setFormat12Hour", format)
views.setCharSequence(R.id.clock_view, "setFormat24Hour", format)
views.setTextColor(R.id.clock_view, color)
}
I have a service that gets started, to listen for preference changes and receive broadcast intents, which calls the widget's onUpdate() whenever something happens.
Everything works fine in the battery function: I can use the configuration activity to change the tint color and whether or not the percentage is shown, and the changes take place immediately. For whatever, reason, though, the functions in the clock function don't seem to do a thing.
When the view is inflated, and I have a color already set to tint it, it's still white. If I change the format, nothing happens. It's almost as if the TextClock functions aren't actually being called.
I even checked the AOSP source, and it looks like these methods should be allowed from a RemoteViews object: setFormat12Hour(CharSequence charSequence)
What'd I do wrong?
Well I figured it out, and I honestly have no idea why it's like this. I noticed that when I added another widget to the info screen, it, too, was not responding to changes. So I looked at the differences between the two broken views and the one working view (the battery).
I found that the battery view is enclosed in a LinearLayout, and wouldn't you know it, enclosing the TextClock and the new widget (and ImageView) in dummy LinearLayouts fixed the issue:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextClock
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="2dp"
android:textSize="64px"
android:id="#+id/textClock" />
</LinearLayout>

Categories

Resources