I haven’t written an article for several months. It’s not because I’m lazy, but NOW I find that I have already entered another stage of my life, and I have less and less time for myself.

Remember before someone asked under the article, Huawei smart screen that focus box implementation. For manufacturers, the most efficient implementation of the priority is to write in c++, after all, the efficiency of the Android drawing is far less efficient than that of the bottom. It happened that some time ago, a friend of mine had a similar need in his company, and his own personal project developed by himself happened to have similar effect, so he took time to write a general component for his friend with the upper layer implementation.



It’s called Halo, and it’s been out of the game for years, in honor of the Chief Corporal. Without digression, let’s begin the text.

Let’s take a look at the effect of Huawei smart screen. When the focus is selected by the poster, button, TAB, these components have a halo effect in the outer ring around the rotation, to tell the truth, in the TV manufacturers of various customized systems, the focus of the dynamic effect design is really much better than other home.

Contact with hongmeng development of the students, will find that even if the use of native button will have this effect, it can be speculated that the system should be dealing with these basic controls. The independent application development, we always can’t control each go to custom made changes again, as some seamless SDK implementation, although it is unified by the Factory interception, the native controls replace custom corresponding controls, but inside still need to maintain a series of custom controls, to the corresponding adapter to replace native controls. So, first of all, we’re going to get rid of customizing basic controls like Button, TextView, CardView, etc. I honestly don’t have the energy or time to do it all. Therefore, it is natural to consider a lightweight ViewGroup: FrameLayout when deciding on a target scheme to implement by wrapping child controls in wrappers.

Let’s take a look at the effect of the implementation, GIF in order to compress the file size, frame down accelerated, is actually very smooth. Support rectangle, rounded corner, round three types, support ring color Settings, surround speed, etc. :

There is one area here that actually stuck me for quite a while. Note that the effect on the Smart screen is that the halo and the inner content area are transparent. In this way, it is not easy to draw directly on the canvas. Initially, I came up with two solutions:

  1. rightcanvasforsaveAnd then presspathCut and then draw halo, restorecanvasAfter drawing the content area. This does allow for transparency between the halo and the content, butclipPathThere is a known fatal flaw: serrations! Of course, TO verify the effect, I implemented it again, but the result was a bit unexpected. Bottom line: Performance is efficient, and it does have a noticeable jagged edge on the TV and some of the lower versions of the phone, especially the roundness. But on my OnePlus 8, Android 11,clipPathIs even smoother than plan 2 below. This makes me embarrassed, the specific reason is unknown, guess the system level has been optimized, please inform those who know.
  2. usePorterDuffXfermodeMixed mode. These more than 10 models, simple and simple, complex and complex, pit is a lot of, you according to the description and the official to the mixed renderings to write their own, very high probability will not appear the results of the official renderings. Hybrid mode go see the officialdemoWell, just to be brief, the hybrid mode has to bebitmapAnd be carefulsrcanddstOrder of precedence. Here is a concrete implementation of interval transparency through mixed mode:

implementation

  1. The first thing we’ll focus on is the halo effect, which moves around the content in motion execution, which, if you think about it, is essentially rotation. Gradient effect and need to keep the same gradient in the position of the outer ring movement during rotation, preferredSweepGradientLet’s start with the code to create the aura:
    private fun createHalo(a) {
        if (width > 0 && height > 0) {
            valshaderBound = sqrt((width * width + height * height).toDouble()).toInt() shaderBitmap = Bitmap.createBitmap(shaderBound,  shaderBound, Bitmap.Config.ARGB_8888)val shaderCanvas = Canvas(shaderBitmap)
            val shaderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
                / / 0.625 0.75 0.875
                // +++++++++++++++++
                / / white 0.5 + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 0 white
                // +++++++++++++++++
                / / 0.375 0.25 0.125
                val shader = SweepGradient(shaderBound / 2f, shaderBound / 2f,
                        intArrayOf(haloColor, Color.TRANSPARENT, Color.TRANSPARENT, haloColor, Color.TRANSPARENT, Color.TRANSPARENT, haloColor),
                        floatArrayOf(0f.0.125 f.0.375 f.0.5 f.0.625 f.0.875 f.1f))this.shader = shader
            }
            shaderCanvas.drawCircle(shaderBound / 2f, shaderBound / 2f, shaderBound.toFloat(), shaderPaint)
            shaderLeft = -(shaderBound - width) / 2f
            shaderTop = -(shaderBound - height) / 2f}}Copy the code

Let’s look at the picture below. White is ourscanvasThe region, our haloshaderIt is a circle and should always surround the outer frame of the content area during rotationshaderThe radius of the circle of PI is PIcanvasHalf of the diagonal of theta. The above mentioned hybrid mode is applied to phibitmap, so we need to putshaderDraw to onebitmapUp, and this onebitmapThe size of is shown in the figure:

  1. At this point, we have completed the first step, creating a halo effect. Speaking of transparent intervals between auras and content areas, how do you do that in hybrid mode? Some of you know thatSurfaceViewThe principle of,Dig a holeI think you’ve heard of it. And that’s what I’m going to do here, through oneDig a hole bitmapWith the halobitmapTo create a transparent area in the middle of the entity’s halo map for content to draw,haloStrokeWidthIs the width of our ring, left, right, top and bottom minus the width of our ring, and the restcanvasThe region is where we draw the content:
    private fun createHole(a) {
        if (width > 0 && height > 0) {
            val holeWidth = width - haloStrokeWidth * 2
            val holeHeight = height - haloStrokeWidth * 2
            holeBitmap = Bitmap.createBitmap(holeWidth.toInt(), holeHeight.toInt(), Bitmap.Config.ARGB_8888)
            val holeCanvas = Canvas(holeBitmap)
            val holePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
                color = Color.WHITE
                style = Paint.Style.FILL
            }
            when (shapeType) {
                SHAPE_RECT -> {
                    holeCanvas.drawRect(0f.0f, holeWidth, holeHeight, holePaint)
                }
                SHAPE_ROUND_RECT -> {
                    holeCanvas.drawRoundRect(0f.0f, holeWidth, holeHeight, cornerRadius.toFloat(), cornerRadius.toFloat(), holePaint)
                }
                SHAPE_CIRCLE -> {
                    holeCanvas.drawCircle(holeWidth / 2f, holeHeight / 2f, holeWidth / 2f, holePaint)
                }
            }
        }
    }
Copy the code
  1. At this point, we have createdshaderBitmapandholeBitmapTwo pictures. At the beginning of the mixing operation, we first draw and process the outer ring ring through mixing, and then hand over the remaining area to the original drawing process for content area (ChildView). At the same time we build a baseValueAnimateAnimation operation, continuous rotation redraw can create a halo around the moving effect:
    private val holePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
    }

    override fun dispatchDraw(canvas: Canvas?). {
        if(isFocused && canvas ! =null) {
            canvas.drawBitmap(holeBitmap, haloStrokeWidth, haloStrokeWidth, null)
            canvas.let {
                canvas.save()
                canvas.rotate(degrees, centerX, centerY)
                canvas.drawBitmap(shaderBitmap, shaderLeft, shaderTop, holePaint)
                canvas.restore()
            }
        }
        super.dispatchDraw(canvas)
    }
Copy the code
  1. That’s all that’s left of the core code, and all that’s left is some general manipulation of shape types, resource release, custom properties, and exposed parameter Settings.
  2. Finally, take a look at how to use:
    <com.seagazer.halo.Halo
        android:id="@+id/halo2"
        android:layout_width="230dp"
        android:layout_height="150dp"
        android:layout_marginStart="30dp"
        app:haloColor="#FFFF61" // Ring color
        app:haloCornerRadius="10dp" // Ring fillet (required when setting fillet)
        app:haloInsertEdge="8dp" // The space between the ring and the content (not less than the width of the ring)
        app:haloShape="roundRect" // Ring type: right Angle, rounded, round
        app:haloWidth="3dp">// Ring width

        <androidx.cardview.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:cardBackgroundColor="@color/halo_card"
            app:cardCornerRadius="8dp">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:text="Round Rect"
                android:textColor="@color/white"
                android:textSize="18sp" />
        </androidx.cardview.widget.CardView>
    </com.seagazer.halo.Halo>
Copy the code

Time is late, old, have to rest early, also don’t write more, complete code and demo we go to see, everybody also don’t get used to stay up late, as a brick dog, dog life or get. Give me a thumbs up if you like. Project address: github.com/seagazer/ha…