afohrman 0aa470af86 Support custom backgrounds for outline text fields and use EditText's background for the text field's outline.
The outline box styles previously didn't allow a custom drawable to be set. The box background was handled by TextInputLayout, which nulled out the EditText's background and drew the boxBackground directly to the canvas. That meant that a call to TextInputEditText#setBackground() would not actually do anything - it would result in the same outline text field.

In order to respect the background drawable, TextInputEditText's background is set to null in the style. Then, TextInputLayout programmatically sets the custom outline drawable if the background is already null. This avoids overwriting a drawable that may have already been set on the TextInputEditText.

This change does pretty much nothing to the filled box style; since the EditText's underline is still being used, the background of the EditText was never set to null for that style. This change should be a no-op for that style.

This move to using the EditText's background for the outline also improves the bounds of the outline text field and gets rid of the cutoff that was appearing at the top of the outline when TextInputLayout's hint was disabled and a hint was set on the EditText.

PiperOrigin-RevId: 230538561
2019-01-25 13:55:36 -05:00

130 lines
3.9 KiB
Java

/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.material.textfield;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.ShapeAppearanceModel;
import android.view.View;
/**
* A {@link MaterialShapeDrawable} that can draw a cutout for the label in {@link TextInputLayout}'s
* outline mode.
*/
class CutoutDrawable extends MaterialShapeDrawable {
private final Paint cutoutPaint;
private final RectF cutoutBounds;
private int savedLayer;
CutoutDrawable() {
this(null);
}
CutoutDrawable(@Nullable ShapeAppearanceModel shapeAppearanceModel) {
super(shapeAppearanceModel != null ? shapeAppearanceModel : new ShapeAppearanceModel());
cutoutPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
setPaintStyles();
cutoutBounds = new RectF();
}
private void setPaintStyles() {
cutoutPaint.setStyle(Style.FILL_AND_STROKE);
cutoutPaint.setColor(Color.WHITE);
cutoutPaint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT));
}
boolean hasCutout() {
return !cutoutBounds.isEmpty();
}
void setCutout(float left, float top, float right, float bottom) {
// Avoid expensive redraws by only calling invalidateSelf if one of the cutout's dimensions has
// changed.
if (left != cutoutBounds.left
|| top != cutoutBounds.top
|| right != cutoutBounds.right
|| bottom != cutoutBounds.bottom) {
cutoutBounds.set(left, top, right, bottom);
invalidateSelf();
}
}
void setCutout(RectF bounds) {
setCutout(bounds.left, bounds.top, bounds.right, bounds.bottom);
}
void removeCutout() {
// Call setCutout with empty bounds to remove the cutout.
setCutout(0, 0, 0, 0);
}
@Override
public void draw(@NonNull Canvas canvas) {
preDraw(canvas);
super.draw(canvas);
// Draw mask for the cutout.
canvas.drawRect(cutoutBounds, cutoutPaint);
postDraw(canvas);
}
private void preDraw(@NonNull Canvas canvas) {
Callback callback = getCallback();
if (useHardwareLayer(callback)) {
View viewCallback = (View) callback;
// Make sure we're using a hardware layer.
if (viewCallback.getLayerType() != View.LAYER_TYPE_HARDWARE) {
viewCallback.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
} else {
// If we're not using a hardware layer, save the canvas layer.
saveCanvasLayer(canvas);
}
}
private void saveCanvasLayer(@NonNull Canvas canvas) {
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
savedLayer = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null);
} else {
savedLayer =
canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);
}
}
private void postDraw(@NonNull Canvas canvas) {
if (!useHardwareLayer(getCallback())) {
canvas.restoreToCount(savedLayer);
}
}
private boolean useHardwareLayer(Callback callback) {
return callback instanceof View;
}
}