How to fully mimic Action item view in the toolbar, for a customized one?
Asked Answered
E

1

4

Background

I have an action item that's not quite standard. It has its own layout, because I've failed to create a nice Drawable for it (written about it here).

Basically, it's just a TextView with a background that wraps its short text that it shows.

The problem

Sadly I can't find a way of fully mimic it to look like normal ones:

  1. The ripple effect (and probably the simple clicking effect on older versions too) doesn't have the same color and size.
  2. There isn't a toast for when I long click on it
  3. Maybe other things I haven't noticed?

Here are screenshots to demonstrate the differences:

The new, non standard, action item:

enter image description here

A native action item:

enter image description here

What I've tried

For the color of the ripple effect, I think I can use "colorControlHighlight", but I can't find out which color is the one used by default, correctly. I've looked inside the "values.xml" file of the support library, and noticed that it's "ripple_material_dark" color (or "ripple_material_light", in case the toolbar is supposed to be white) , but this seems a bit like a hack.

Not sure about the size, but looking at the layout inspector, I think the view has padding:

enter image description here

I've also noticed that the view class name of the toolbar is ActionMenuItemView . I tried to look at its code (probably avaialble online too, here) , but didn't notice anything mentioned there about background. For padding I think it just tries to put the icon in the middle:

enter image description here

Anyway, this is the current code of the POC:

MainActivity.kt

class MainActivity : AppCompatActivity() {
    lateinit var goToTodayView: View
    lateinit var goToTodayTextView: TextView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
        goToTodayView = LayoutInflater.from(this).inflate(R.layout.go_to_today_action_item, toolbar, false)
        goToTodayTextView = goToTodayView.goToTodayTextView
        goToTodayTextView.setBackgroundDrawable(AppCompatResources.getDrawable(this, R.drawable.ic_backtodate))
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menu.add("goToToday").setActionView(goToTodayView).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
        menu.add("asd").setIcon(R.drawable.abc_ic_menu_copy_mtrl_am_alpha).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) //for comparison
        return super.onCreateOptionsMenu(menu)
    }
}

go_to_today_action_item.xml

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
    android:background="?attr/selectableItemBackgroundBorderless" android:backgroundTint="#fff" android:clickable="true"
    android:focusable="true">

    <TextView
        android:id="@+id/goToTodayTextView" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:layout_gravity="center" android:gravity="center" android:text="1"
        android:textColor="#fff" android:textSize="12dp" tools:background="@drawable/ic_backtodate" tools:layout_gravity="center"/>
</FrameLayout>

The questions

  1. How do I set the same background as a normal action item?
  2. How do I put the toast like a normal action item?
  3. Are there other things that are different between normal action item and one that I set with a layout?
  4. In other words, is it possible to fully mimic native action items? Maybe using the ActionMenuItemView class somehow? Maybe a very different solution from what I tried?

EDIT: for the background of the action items, I made it a bit like the original ones, but it's still not the same. Clicking effect seems a tiny bit different, and I haven't found how to show the toast of the action item upon long clicking on it. Here's the result:

enter image description here

Anyway, here's what I did:

go_to_today_action_item.xml

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="?attr/actionBarSize" android:layout_height="?attr/actionBarSize" android:clickable="true"
    android:focusable="true">

    <ImageView
        android:layout_width="@dimen/action_item_background_size"
        android:layout_height="@dimen/action_item_background_size" android:layout_gravity="center"
        android:background="@drawable/action_item_selector" android:duplicateParentState="true"/>

    <TextView
        android:id="@+id/goToTodayTextView" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:layout_gravity="center" android:gravity="center" android:text="31" android:textColor="#fff"
        android:textSize="12dp" tools:background="@drawable/ic_backtodate" tools:layout_gravity="center"/>
</FrameLayout>

drawable/action_item_selector.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <solid android:color="@color/clicking_semi_white_ripple_color"/>
        </shape>
    </item>
    <item android:drawable="@android:color/transparent"/>
</selector>

drawable-v21/action_item_selector.xml

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="@color/clicking_semi_white_ripple_color">
    <item android:id="@android:id/mask">
        <shape android:shape="oval">
            <solid android:color="@android:color/white"/>
        </shape>
    </item>
</ripple>

colors.xml

<color name="clicking_semi_white_ripple_color">#33ffffff</color>

values/dimens.xml

<dimen name="action_item_background_size">48dp</dimen>

values-v21/dimens.xml

<dimen name="action_item_background_size">40dp</dimen>
Excess answered 7/12, 2017 at 15:6 Comment(4)
Can you provide any demo project for this?Rinee
@HardikChauhan OK, here: files.fm/u/sxbxw8apExcess
in which older os version it's not working properly? As per the toast, it shows when you set the icon if you change it to text drawable or custom view it won't work.Rinee
@HardikChauhan It's not that it doesn't work properly. It's just that I want to mimic it to look exactly the same. And I was talking about Android versions before the ripple effect (example : Android 4.x). About the toast, a drawable does actually work (see here one solution I've found for this: https://mcmap.net/q/737839/-how-to-have-an-image-with-a-dynamic-text-in-it-all-in-a-drawable-like-the-quot-today-quot-action-item-on-google-calendar-app ) , but I don't think a custom view will.Excess
H
2

The following code will use your layout to populate a standard menu item for the date. As a standard menu item, it will exhibit all the characteristics that you are looking for and should be compatible with future changes to a menu item's behavior.

The basic concept is to inflate the layout with the boxed date and use its drawing cache to create a bitmap that is then used as the drawable for the menu item's icon.

MainActivity.kt

class MainActivity : AppCompatActivity() {
    private var goToTodayView: View? = null
    private var goToTodayTextView: TextView? = null
    private val textDrawable: TextDrawable? = null
    private var mOptionsMenu: Menu? = null

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(findViewById<View>(R.id.toolbar) as Toolbar)
        testWithCustomViews()
    }

    private fun updateDateView() {
        if (mOptionsMenu == null)
            return
        goToTodayView!!.invalidate()
        goToTodayView!!.buildDrawingCache()
        val bmp = Bitmap.createBitmap(goToTodayView!!.drawingCache)
        val d = BitmapDrawable(resources, bmp)
        mOptionsMenu!!.getItem(0).icon = d
    }

    private fun testWithCustomViews() {
        val toolbar = findViewById<View>(R.id.toolbar) as Toolbar

        goToTodayView = LayoutInflater.from(this).inflate(R.layout.go_to_today_action_item, toolbar, false)
        goToTodayView!!.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
        goToTodayView!!.layout(0, 0, goToTodayView!!.measuredWidth, goToTodayView!!.measuredHeight)
        goToTodayTextView = goToTodayView!!.findViewById(R.id.goToTodayTextView)
        goToTodayTextView!!.setBackgroundDrawable(AppCompatResources.getDrawable(this, R.drawable.ic_backtodate))
        goToTodayView!!.isDrawingCacheEnabled = true
        val handler = Handler()
        val runnable = object : Runnable {
            internal var i = 0

            override fun run() {
                if (isFinishing || isDestroyed)
                    return
                goToTodayTextView!!.text = (i + 1).toString()
                i = (i + 1) % 31
                updateDateView()
                handler.postDelayed(this, 1000)
            }
        }
        runnable.run()
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        mOptionsMenu = menu
        menu.add("goToToday").setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
        updateDateView()
        menu.add("asd").setIcon(R.drawable.abc_ic_menu_copy_mtrl_am_alpha).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
        return super.onCreateOptionsMenu(menu)
    }
}

Since go_to_today_action_item.xml now doesn't need the extra views to mimic standard menu item behavior, it can be stripped down. In fact, the menu item icon appears too small without adjustments. Change go_to_today_action_item.xml to the following:

<TextView
    android:id="@+id/goToTodayTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:background="@drawable/ic_backtodate"
    android:gravity="center"
    android:text="31"
    android:textColor="#fff"
    android:textSize="12dp"
    tools:layout_gravity="center" />

enter image description here

Hernardo answered 12/12, 2017 at 20:36 Comment(7)
Nice workaround. You just take a "screenshot" of the view that's supposed to be shown, and use it as a normal icon. But, using the code above, I get the image to be too small for some reason. Try to import the project here: #47698498 , and then try your code. You can see this as a result: ibb.co/h7PJC6Excess
ok seems to work fine now. Wish it wouldn't have had used the bitmap workaround though, but maybe that's the best we can do with what we have?Excess
@androiddeveloper It does give you the drawable you were seeking on the other question. A second way would be to produce 31 drawables that can be loaded directly. IMO, separate icon files, although easier to understand what is going on, is less desirable. Maybe another poster will have a better idea but this one is not so bad.Hernardo
It's great that it works. Really. And it's "not so bad" at all for this case, as it's just a small bitmap for a very specific task. Just thought there might be a better solution which doesn't need a bitmap. Since the Activity I'm working on is a bit heavy in UI, I'm trying to use all the tools I can to make it work as smoothly as possible. Anyway, I will mark this answer and if no other solution is found, will grant the bounty. The reason is that I've chosen the other way anyway: https://mcmap.net/q/737839/-how-to-have-an-image-with-a-dynamic-text-in-it-all-in-a-drawable-like-the-quot-today-quot-action-item-on-google-calendar-app . Would appreciate if you can help there too, in some way.Excess
Again, don't get me wrong. This is a nice solution, but I consider this a workaround, because it doesn't really make you have the view inside there. More like a "twin" bitmap of it. I hope there is a better way, just for knowing...Excess
@androiddeveloper I have to admit that I didn't really read the other question. I now see that you create a custom Drawable that you are using in setIcon(Drawable). So, you are not happy with the way the graphic looks with that solution? Is that the open issue? If so, off the top of my head, I would say that you could embed this solution into that solution to give you a drawable for setting the icon. You could just create the necessary views programmatically and not worry about externalities.Hernardo
A drawable can be set as the icon, so no need to convert it to a bitmap. That's also what I did. However, here on this thread, I asked about a customized view (meaning not what the action item normally has). What you've provided is not what I had in mind. I consider your solution as a workaround, because it actually removes the custom view, and replaces it with a bitmap. There are various disadvantages to it (a bit of performance, a bit more memory, and inability to access it via automatic-tests, for example), but still, it works.Excess

© 2022 - 2024 — McMap. All rights reserved.