Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

Press the effector

Android 5.0 + Button default shadow effect, this is to increase the stereoscopic and visual effects, is very good. But the product, in a word: “IOS!” To remove the painful Android development, we can add the following code to the XML:

style="? android:attr/borderlessButtonStyle"Copy the code

This doesn’t work because the Button on IOS has a press transparency effect, so we need to add a transparency effect.

Let’s start with a simple transparency utility class:

public class PressEffect { public static void TouchEffect(View view, int action) { if (view == null) return; if (! view.isClickable()) return; If (motionEvent.action_down == action) {// close to 0.618 } else {the setAlpha (1.0 f); }}}Copy the code

Next, we can define a Button and call it from onTouch:

public class CButton extends Button { @Override public boolean onTouchEvent(MotionEvent event) { PressEffect.TouchEffect(this, event.getAction()); return super.onTouchEvent(event); }}Copy the code

Of course, you can customize any View, not just a Button, by copying its onTouch() event and setting the clickable property to true.

Some people say it’s too much work to customize the View, but that’s not true. What if our project had to (or might in the future) treat all the interfaces of the app as a whole? If you use custom views, you just need to crosscut all the views, but if you don’t, you might struggle. So we have to think about the future.

For example, the following App black and white processing.

Black and white pattern

The following code sets the View to black and white mode:

public static void darkTheme(View view) {
    if (view == null) return;
    Paint paint = new Paint();
    ColorMatrix colorMatrix = new ColorMatrix();
    colorMatrix.setSaturation(0);
    paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
    view.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
}
Copy the code

This is done by setting saturation.

The following code restores the black and white mode:

public static void resetTheme(View view) {
    if (view == null) return;
    view.setLayerType(View.LAYER_TYPE_HARDWARE, new Paint());
}
Copy the code

This looks like this (passing the Activity’s DecorView):

We can see that clicking Dark will change the mode to black and white, and clicking Reset will restore the mode. And, Dark button when pressed is no shadow, but become transparent, lift is restored; Clicking Reset is shaded. This is because Dark uses the CButton and PressEffect defined above.

If the entire App needs to be black and white, there are two options:

  • 1 Traverse each Activity/Dialog and get a DecorView to set it up.
  • Define a complete set of custom views and work within those views.

Scheme 2 is flexible and non-intrusive to the business. You can define a set of TextView, Button, LinearLayout, etc., inherited from the system. If there are subsequent changes, you can unify them in these custom views (because you can’t change the system’s own View).

The pit of the Switch

We know that for the Switch, if OnCheckedChangeListener is set, calling switch.setChecked(true) will trigger the callback in the event, causing some unnecessary logic to be done, which is incorrect.

In plain English, what we want is that if the Checked property is set by code, it won’t fire. If the user clicked on it, it would trigger, so we can determine whether to press it or not:

Switch. SetOnCheckedChangeListener {buttonView, isChecked - > / / if there is no press, are considered code sets, directly to intercept the if (! buttonView.isPressed()){ return; } / /... Other business}Copy the code

I’m not going to show it here.

Connect point interceptor

In our business, there are many problems caused by frequent clicking of users, such as clicking a button to refresh data. If you click it frequently, you will request the interface for many times. If you do not deal with it, a person who has been single for 30 years and is fast may directly kill a server.

Therefore, we need to add protection to avoid frequent clicking. We can add an interval to the click event, less than this interval will not trigger the click event, as follows:

Public abstract class ClickProtector implements view. OnClickListener {ms private long delay = 0; // The actual click event abstract void onRealClick(View v); /** * public ClickProtector delay(long delay) {this.delay = delay; return this; } @Override public void onClick(View v) { int key = v.hashCode(); Long lastTime = (Long) v.getTag(key); // Block if (lastTime! = null && System.currentTimeMillis() - lastTime < delay) { return; } // Trigger the click event onRealClick(v); V.settag (key, system.currentTimemillis ()); }}Copy the code

The effect is as follows:

Only Add sets the interceptor as follows:

findViewById<Button>(R.id.btn_dark).setOnClickListener(object : ClickProtector() {
    override fun onRealClick(v: View?) {
        tvCount.text = "${++number}"
    }
}.delay(1000))
Copy the code

We set the delay to 1000, that is, to trigger once in 1s.

Of course, you can also improve the click protector, such as adding a “blocking callback”, can give users a prompt message, etc. As follows:

Private List<Runnable> runnables = new ArrayList<>(); // Block if (lastTime! = null && system.currentTimemillis () -lastTime < delay) {// Execute intercepting callback for (Runnable Runnable: runnables) { runnable.run(); } return; }Copy the code

You can also extend other interceptors.