Skip to content

Commit

Permalink
feat(android): support touch feedback for all backgrounds
Browse files Browse the repository at this point in the history
- Used to only be supported when using "backgroundColor" property.
- Now applied to all background types such as:
  * backgroundImage
  * backgroungGradient
  * No background

Fixes TIMOB-26315
  • Loading branch information
jquick-axway authored and sgtcoolguy committed Jul 13, 2020
1 parent 3c7c118 commit 2a0b1be
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 42 deletions.
Expand Up @@ -42,9 +42,9 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import android.text.TextUtils;
import android.util.Pair;
Expand Down Expand Up @@ -813,8 +813,6 @@ public void propertyChanged(String key, Object oldValue, Object newValue, KrollP
boolean hasColorState = hasColorState(d);
boolean hasBorder = hasBorder(d);
boolean hasGradient = hasGradient(d);
boolean nativeViewNull = (nativeView == null);

boolean requiresCustomBackground = hasImage || hasColorState || hasBorder || hasGradient;

// PROPERTY_BACKGROUND_REPEAT is implicitly passed as false though not used in JS. So check the truth value and proceed.
Expand All @@ -830,16 +828,11 @@ public void propertyChanged(String key, Object oldValue, Object newValue, KrollP
background = null;
}

if (d.containsKeyAndNotNull(TiC.PROPERTY_BACKGROUND_COLOR)) {
Integer bgColor = TiConvert.toColor(d, TiC.PROPERTY_BACKGROUND_COLOR);
if (!nativeViewNull) {
if (canApplyTouchFeedback(d)) {
applyTouchFeedback(bgColor, d.containsKey(TiC.PROPERTY_TOUCH_FEEDBACK_COLOR)
? TiConvert.toColor(d, TiC.PROPERTY_TOUCH_FEEDBACK_COLOR)
: null);
} else {
nativeView.setBackgroundColor(bgColor);
}
if (this.nativeView != null) {
if (d.containsKeyAndNotNull(TiC.PROPERTY_BACKGROUND_COLOR)) {
this.nativeView.setBackgroundColor(TiConvert.toColor(d, TiC.PROPERTY_BACKGROUND_COLOR));
} else {
this.nativeView.setBackground(null);
}
}
} else {
Expand Down Expand Up @@ -896,11 +889,15 @@ public void propertyChanged(String key, Object oldValue, Object newValue, KrollP

applyCustomBackground();
}
if (canApplyTouchFeedback(d)) {
String colorString = TiConvert.toString(d.get(TiC.PROPERTY_TOUCH_FEEDBACK_COLOR));
applyTouchFeedback((colorString != null) ? TiConvert.toColor(colorString) : null);
}
if (key.equals(TiC.PROPERTY_OPACITY)) {
setOpacity(TiConvert.toFloat(newValue, 1f));
}
if (!nativeViewNull) {
nativeView.postInvalidate();
if (this.nativeView != null) {
this.nativeView.postInvalidate();
}
} else if (key.equals(TiC.PROPERTY_SOFT_KEYBOARD_ON_FOCUS)) {
Log.w(TAG,
Expand Down Expand Up @@ -1008,26 +1005,22 @@ public void processProperties(KrollDict d)
handleBackgroundImage(d);

} else if (d.containsKey(TiC.PROPERTY_BACKGROUND_COLOR) && !nativeViewNull) {
// Set the background color on the view directly only if there is no border.
// If border is present, then we must use the TiBackgroundDrawable.
bgColor = TiConvert.toColor(d, TiC.PROPERTY_BACKGROUND_COLOR);

if (canApplyTouchFeedback(d)) {
applyTouchFeedback(bgColor, d.containsKey(TiC.PROPERTY_TOUCH_FEEDBACK_COLOR)
? TiConvert.toColor(d, TiC.PROPERTY_TOUCH_FEEDBACK_COLOR)
: null);
} else {
// Set the background color on the view directly only
// if there is no border. If a border is present we must
// use the TiBackgroundDrawable.
if (hasBorder(d)) {
if (background == null) {
applyCustomBackground(false);
}
background.setBackgroundColor(bgColor);
} else {
nativeView.setBackgroundColor(bgColor);
if (hasBorder(d)) {
if (background == null) {
applyCustomBackground(false);
}
background.setBackgroundColor(bgColor);
} else {
nativeView.setBackgroundColor(bgColor);
}
}
if (canApplyTouchFeedback(d)) {
String colorString = TiConvert.toString(d.get(TiC.PROPERTY_TOUCH_FEEDBACK_COLOR));
applyTouchFeedback((colorString != null) ? TiConvert.toColor(colorString) : null);
}

if (d.containsKey(TiC.PROPERTY_HIDDEN_BEHAVIOR) && !nativeViewNull) {
Object hidden = d.get(TiC.PROPERTY_HIDDEN_BEHAVIOR);
Expand Down Expand Up @@ -1188,23 +1181,51 @@ protected boolean canApplyTouchFeedback(@NonNull KrollDict props)

/**
* Applies touch feedback. Should check canApplyTouchFeedback() before calling this.
* @param backgroundColor The background color of the view.
* @param rippleColor The ripple color.
* @param rippleColor The ripple color to use. Set to null to use system's default ripple color.
*/
private void applyTouchFeedback(@NonNull Integer backgroundColor, @Nullable Integer rippleColor)
private void applyTouchFeedback(Integer rippleColor)
{
// Do not continue if there is no view to modify.
if (this.nativeView == null) {
return;
}

// Fetch default ripple color if given null.
if (rippleColor == null) {
Context context = proxy.getActivity();
TypedValue attribute = new TypedValue();
if (context.getTheme().resolveAttribute(android.R.attr.colorControlHighlight, attribute, true)) {
rippleColor = attribute.data;
} else {
throw new RuntimeException("android.R.attr.colorControlHighlight cannot be resolved into Drawable");
}
if (rippleColor == null) {
Log.e(TAG, "android.R.attr.colorControlHighlight cannot be resolved into Drawable");
return;
}
}
RippleDrawable rippleDrawable =
new RippleDrawable(ColorStateList.valueOf(rippleColor), new ColorDrawable(backgroundColor), null);
nativeView.setBackground(rippleDrawable);

// Fetch the background drawable that we'll be applying the ripple effect to.
Drawable backgroundDrawable = this.background;
if (backgroundDrawable == null) {
backgroundDrawable = this.nativeView.getBackground();
}

// Create a mask if a background doesn't exist or if it's completely transparent.
// Note: Ripple effect won't work unless it has something opaque to draw to. Use mask as a fallback.
ShapeDrawable maskDrawable = null;
boolean isVisible = (backgroundDrawable != null);
if (backgroundDrawable instanceof ColorDrawable) {
int colorValue = ((ColorDrawable) backgroundDrawable).getColor();
if (Color.alpha(colorValue) <= 0) {
isVisible = false;
}
}
if (!isVisible) {
maskDrawable = new ShapeDrawable();
}

// Replace view's existing background with ripple effect wrapping the old drawable.
nativeView.setBackground(
new RippleDrawable(ColorStateList.valueOf(rippleColor), backgroundDrawable, maskDrawable));
}

@Override
Expand Down
7 changes: 5 additions & 2 deletions apidoc/Titanium/UI/View.yml
Expand Up @@ -1769,8 +1769,11 @@ properties:
- name: touchFeedback
summary: A material design visual construct that provides an instantaneous visual confirmation of touch point.
description: |
This is an opt-in feature available from Android Lollipop.
Touch feedback is applied only if the backgroundColor is a solid color.
Touch feedback is only applied to a view's background. It is never applied to the view's foreground content
such as a <Titanium.UI.ImageView>'s image.
For Titanium versions older than 9.1.0, touch feedback only works if you set the
<Titanium.UI.View.backgroundColor> property to a non-transparent color.
type: Boolean
default: false
platforms: [android]
Expand Down
76 changes: 75 additions & 1 deletion tests/Resources/ti.ui.view.addontest.js
@@ -1,6 +1,6 @@
/*
* Appcelerator Titanium Mobile
* Copyright (c) 2015-Present by Appcelerator, Inc. All Rights Reserved.
* Copyright (c) 2015-Present by Axway, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
Expand Down Expand Up @@ -174,4 +174,78 @@ describe('Titanium.UI.View', function () {
win.open();
});
});

it.android('touchFeedback', finish => {
win = Ti.UI.createWindow({ layout: 'horizontal' });
win.add(Ti.UI.createLabel({
text: 'View 1',
touchFeedback: true,
touchFeedbackColor: 'yellow'
}));
win.add(Ti.UI.createLabel({
text: 'View 2',
backgroundColor: 'gray',
touchFeedback: true,
touchFeedbackColor: 'yellow'
}));
win.add(Ti.UI.createLabel({
text: 'View 3',
backgroundImage: '/Logo.png',
touchFeedback: true,
touchFeedbackColor: 'yellow'
}));
win.add(Ti.UI.createLabel({
text: 'View 4',
backgroundGradient: {
type: 'linear',
startPoint: { x: '0%', y: '50%' },
endPoint: { x: '100%', y: '50%' },
colors: [ { color: 'red', offset: 0.0 }, { color: 'blue', offset: 1.0 } ]
},
touchFeedback: true,
touchFeedbackColor: 'yellow'
}));
win.add(Ti.UI.createLabel({
text: 'View 5',
borderRadius: 20,
borderColor: 'red',
borderWidth: '8dp',
touchFeedback: true,
touchFeedbackColor: 'yellow'
}));
win.add(Ti.UI.createLabel({
text: 'View 6',
backgroundColor: 'gray',
borderRadius: 20,
borderColor: 'red',
borderWidth: '8dp',
touchFeedback: true,
touchFeedbackColor: 'yellow'
}));
win.add(Ti.UI.createLabel({
text: 'View 7',
backgroundImage: '/Logo.png',
borderRadius: 20,
borderColor: 'red',
borderWidth: '8dp',
touchFeedback: true,
touchFeedbackColor: 'yellow'
}));
win.add(Ti.UI.createLabel({
text: 'View 8',
backgroundGradient: {
type: 'linear',
startPoint: { x: '0%', y: '50%' },
endPoint: { x: '100%', y: '50%' },
colors: [ { color: 'red', offset: 0.0 }, { color: 'blue', offset: 1.0 } ]
},
borderRadius: 20,
borderColor: 'red',
borderWidth: '8dp',
touchFeedback: true,
touchFeedbackColor: 'yellow'
}));
win.addEventListener('open', () => finish());
win.open();
});
});

0 comments on commit 2a0b1be

Please sign in to comment.