-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Android] Correctly scale Button image (#19834)
* Fixed on Android * Added snapshots * Fix on iOS * Fixed broken test * Added iOS snapshot * Changed logic to resize on iOS (now rescale) * Updated snapshots * Moved changes in Button iOS from Core to Controls * Revert Windows related changes * Revert iOS snapshot * Revert more changes * Resize the image to fit the button * Use the correct measure spec mode * nice things * this * More things * add a comment * rename * Update Issue18242.cs * Add files via upload --------- Co-authored-by: Matthew Leibowitz <[email protected]>
- Loading branch information
1 parent
7283fdc
commit 1b423ab
Showing
8 changed files
with
177 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,169 @@ | ||
using Android.Content; | ||
using System; | ||
using Android.Content; | ||
using Android.Graphics.Drawables; | ||
using Android.Views; | ||
using AndroidX.Core.Widget; | ||
using Google.Android.Material.Button; | ||
|
||
namespace Microsoft.Maui.Platform | ||
{ | ||
public class MauiMaterialButton : MaterialButton | ||
{ | ||
// Currently Material doesn't have any bottom gravity options | ||
// so we just move the layout to the bottom using | ||
// SetCompoundDrawablesRelative during Layout | ||
internal const int IconGravityBottom = 9999; | ||
public MauiMaterialButton(Context context) : base(context) | ||
// The default MaterialButton currently does not have a concept of bottom | ||
// gravity which we need for .NET MAUI. | ||
// In order to get this feature, we have added a custom gravity option | ||
// that serves as a flag to indicate that the icon should be placed at | ||
// the bottom. | ||
// The real gravity value is IconGravityTop in order to perform all the | ||
// normal layout calculations. We then set ForceBottomIconGravity for our | ||
// custom layout pass where we simply swap the icon from the top to the | ||
// bottom using SetCompoundDrawablesRelative. | ||
internal const int IconGravityBottom = 0x1000; | ||
|
||
public MauiMaterialButton(Context context) | ||
: base(context) | ||
{ | ||
} | ||
|
||
protected override void OnLayout(bool changed, int left, int top, int right, int bottom) | ||
public override int IconGravity | ||
{ | ||
// These are hacks that seem to force the button to measure correctly | ||
// when using top or bottom positioning. | ||
if (IconGravity == IconGravityBottom) | ||
get => base.IconGravity; | ||
set | ||
{ | ||
var drawable = TextViewCompat.GetCompoundDrawablesRelative(this)[3]; | ||
drawable?.SetBounds(0, 0, drawable.IntrinsicWidth, drawable.IntrinsicHeight); | ||
// Intercept the gravity value and set the flag if it's bottom. | ||
ForceBottomIconGravity = value == IconGravityBottom; | ||
base.IconGravity = ForceBottomIconGravity ? IconGravityTop : value; | ||
} | ||
else if (IconGravity == MaterialButton.IconGravityTop) | ||
} | ||
|
||
internal bool ForceBottomIconGravity { get; private set; } | ||
|
||
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) | ||
{ | ||
if (Icon is MauiResizableDrawable currentIcon) | ||
{ | ||
var drawable = TextViewCompat.GetCompoundDrawablesRelative(this)[1]; | ||
drawable?.SetBounds(0, 0, drawable.IntrinsicWidth, drawable.IntrinsicHeight); | ||
// if there is BOTH an icon AND text, but the text layout has NOT been measured yet, | ||
// we need to measure JUST the text first to get the remaining space for the icon | ||
if (Layout is null && TextFormatted?.Length() > 0) | ||
{ | ||
// remove the icon and measure JUST the text | ||
Icon = null; | ||
base.OnMeasure(widthMeasureSpec, heightMeasureSpec); | ||
|
||
// restore the icon | ||
Icon = currentIcon; | ||
} | ||
|
||
// determine the total client area available for BOTH the icon AND text to fit | ||
var availableWidth = MeasureSpec.GetMode(widthMeasureSpec) == MeasureSpecMode.Unspecified | ||
? int.MaxValue | ||
: MeasureSpec.GetSize(widthMeasureSpec); | ||
var availableHeight = MeasureSpec.GetMode(heightMeasureSpec) == MeasureSpecMode.Unspecified | ||
? int.MaxValue | ||
: MeasureSpec.GetSize(heightMeasureSpec); | ||
|
||
// calculate the icon size based on the remaining space | ||
CalculateIconSize(currentIcon, availableWidth, availableHeight); | ||
} | ||
|
||
// re-measure with both text and icon | ||
base.OnMeasure(widthMeasureSpec, heightMeasureSpec); | ||
} | ||
|
||
protected override void OnLayout(bool changed, int left, int top, int right, int bottom) | ||
{ | ||
base.OnLayout(changed, left, top, right, bottom); | ||
|
||
// After the layout pass, we swap the icon from the top to the bottom. | ||
if (ForceBottomIconGravity) | ||
{ | ||
var icons = TextViewCompat.GetCompoundDrawablesRelative(this); | ||
if (icons[1] is { } icon) | ||
{ | ||
TextViewCompat.SetCompoundDrawablesRelative(this, null, null, null, icon); | ||
icon.SetBounds(0, 0, icon.IntrinsicWidth, icon.IntrinsicHeight); | ||
} | ||
} | ||
} | ||
|
||
void CalculateIconSize(MauiResizableDrawable resizable, int availableWidth, int availableHeight) | ||
{ | ||
// bail if the text layout is not available yet, this is most likely a bug | ||
if (Layout is null) | ||
{ | ||
return; | ||
} | ||
|
||
var actual = resizable.Drawable; | ||
|
||
var remainingWidth = availableWidth - PaddingLeft - PaddingRight; | ||
var remainingHeight = availableHeight - PaddingTop - PaddingBottom; | ||
|
||
if (IsIconGravityHorizontal) | ||
{ | ||
remainingWidth -= IconPadding + GetTextLayoutWidth(); | ||
} | ||
else | ||
{ | ||
remainingHeight -= IconPadding + GetTextLayoutHeight(); | ||
} | ||
|
||
var iconWidth = Math.Min(remainingWidth, actual.IntrinsicWidth); | ||
var iconHeight = Math.Min(remainingHeight, actual.IntrinsicHeight); | ||
|
||
var ratio = Math.Min( | ||
(double)iconWidth / actual.IntrinsicWidth, | ||
(double)iconHeight / actual.IntrinsicHeight); | ||
|
||
resizable.SetPreferredSize( | ||
Math.Max(0, (int)(actual.IntrinsicWidth * ratio)), | ||
Math.Max(0, (int)(actual.IntrinsicHeight * ratio))); | ||
|
||
// trigger a layout re-calculation | ||
Icon = null; | ||
Icon = resizable; | ||
} | ||
|
||
bool IsIconGravityHorizontal => | ||
IconGravity is IconGravityTextStart or IconGravityTextEnd or IconGravityStart or IconGravityEnd; | ||
|
||
int GetTextLayoutWidth() | ||
{ | ||
float maxWidth = 0; | ||
int lineCount = LineCount; | ||
for (int line = 0; line < lineCount; line++) | ||
{ | ||
maxWidth = Math.Max(maxWidth, Layout!.GetLineWidth(line)); | ||
} | ||
return (int)Math.Ceiling(maxWidth); | ||
} | ||
|
||
int GetTextLayoutHeight() | ||
{ | ||
var layoutHeight = Layout!.Height; | ||
|
||
return layoutHeight; | ||
} | ||
|
||
internal class MauiResizableDrawable : LayerDrawable | ||
{ | ||
public MauiResizableDrawable(Drawable drawable) | ||
: base([drawable]) | ||
{ | ||
PaddingMode = (int)LayerDrawablePaddingMode.Stack; | ||
} | ||
|
||
public Drawable Drawable => GetDrawable(0)!; | ||
|
||
public void SetPreferredSize(int width, int height) | ||
{ | ||
if (OperatingSystem.IsAndroidVersionAtLeast(23)) | ||
{ | ||
SetLayerSize(0, width, height); | ||
} | ||
|
||
// TODO: find something that works for older versions | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters