refactor(android): v3.0 ripple background

This commit is contained in:
maxli
2022-03-10 11:14:53 +08:00
committed by siguangli
parent d1f4d3b4ee
commit 1fa9d64bed
6 changed files with 259 additions and 83 deletions
@@ -128,7 +128,7 @@ public class NodeProps {
public static final String ON_LONG_CLICK = "longclick";
public static final String ON_PRESS_IN = "pressin";
public static final String ON_PRESS_OUT = "pressout";
public static final String ON_TOUCH_DOWN = "touchdown";
public static final String ON_TOUCH_DOWN = "touchstart";
public static final String ON_TOUCH_MOVE = "touchmove";
public static final String ON_TOUCH_END = "touchend";
public static final String ON_TOUCH_CANCEL = "touchcancel";
@@ -171,6 +171,7 @@ public class NodeProps {
public static final String VISIBLE = "visible";
public static final String REPEAT_COUNT = "repeatCount";
public static final String ATTRIBUTES = "attributes";
public static final String BACKGROUND_RIPPLE = "nativeBackgroundAndroid";
private static final HashSet<String> JUST_LAYOUT_PROPS = new HashSet<>(
Arrays.asList(ALIGN_SELF, ALIGN_ITEMS, COLLAPSABLE, FLEX, FLEX_DIRECTION, FLEX_WRAP,
@@ -13,11 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.mtt.hippy.views.view;
import static com.tencent.renderer.component.drawable.DrawableFactory.DrawableType.DRAWABLE_TYPE_RIPPLE;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.View;
import androidx.annotation.NonNull;
import com.tencent.mtt.hippy.annotation.HippyController;
import com.tencent.mtt.hippy.annotation.HippyControllerProps;
import com.tencent.mtt.hippy.dom.node.NodeProps;
@@ -26,74 +33,116 @@ import com.tencent.mtt.hippy.utils.PixelUtil;
import com.tencent.mtt.hippy.views.image.HippyImageView;
import com.tencent.renderer.NativeRenderContext;
import com.tencent.renderer.component.drawable.DrawableFactory;
import com.tencent.renderer.utils.ArrayUtils;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
@SuppressWarnings({"unused"})
@HippyController(name = HippyViewGroupController.CLASS_NAME)
public class HippyViewGroupController extends HippyGroupController<HippyViewGroup> {
public static final String CLASS_NAME = "View";
public static final String CLASS_NAME = "View";
private static final String FUNC_SET_PRESSED = "setPressed";
private static final String FUNC_SET_HOTSPOT = "setHotspot";
private static final WeakHashMap<View, Integer> mZIndexHash = new WeakHashMap<>();
public static final WeakHashMap<View, Integer> mZIndexHash = new WeakHashMap<>();
public static void setViewZIndex(View view, int zIndex) {
mZIndexHash.put(view, zIndex);
}
public static void removeViewZIndex(View view) {
mZIndexHash.remove(view);
}
public static Integer getViewZIndex(View view) {
return mZIndexHash.get(view);
}
@Override
protected View createViewImpl(Context context) {
return new HippyViewGroup(context);
}
@HippyControllerProps(name = NodeProps.OVERFLOW, defaultType = HippyControllerProps.STRING, defaultString = "visible")
public void setOverflow(HippyViewGroup hippyViewGroup, String overflow) {
hippyViewGroup.setOverflow(overflow);
}
@HippyControllerProps(name = NodeProps.BACKGROUND_IMAGE, defaultType = HippyControllerProps.STRING)
public void setBackgroundImage(HippyViewGroup hippyViewGroup, String url) {
hippyViewGroup.setUrl(getInnerPath((NativeRenderContext)hippyViewGroup.getContext(), url));
}
@HippyControllerProps(name = NodeProps.BACKGROUND_SIZE, defaultType = HippyControllerProps.STRING, defaultString = "origin")
public void setBackgroundImageSize(HippyImageView hippyImageView, String resizeModeValue) {
if ("contain".equals(resizeModeValue)) {
// 在保持图片宽高比的前提下缩放图片,直到宽度和高度都小于等于容器视图的尺寸
// 这样图片完全被包裹在容器中,容器中可能留有空白
hippyImageView.setScaleType(HippyImageView.ScaleType.CENTER_INSIDE);
} else if ("cover".equals(resizeModeValue)) {
// 在保持图片宽高比的前提下缩放图片,直到宽度和高度都大于等于容器视图的尺寸
// 这样图片完全覆盖甚至超出容器,容器中不留任何空白
hippyImageView.setScaleType(HippyImageView.ScaleType.CENTER_CROP);
} else if ("center".equals(resizeModeValue)) {
// 居中不拉伸
hippyImageView.setScaleType(HippyImageView.ScaleType.CENTER);
} else if ("origin".equals(resizeModeValue)) {
// 不拉伸,居左上
hippyImageView.setScaleType(HippyImageView.ScaleType.ORIGIN);
} else {
// stretch and other mode
// 拉伸图片且不维持宽高比,直到宽高都刚好填满容器
hippyImageView.setScaleType(HippyImageView.ScaleType.FIT_XY);
public static void setViewZIndex(View view, int zIndex) {
mZIndexHash.put(view, zIndex);
}
}
@HippyControllerProps(name = NodeProps.BACKGROUND_POSITION_X, defaultType = HippyControllerProps.NUMBER)
public void setBackgroundImagePositionX(HippyViewGroup hippyViewGroup, int positionX) {
hippyViewGroup.setImagePositionX((int) PixelUtil.dp2px(positionX));
}
public static void removeViewZIndex(View view) {
mZIndexHash.remove(view);
}
@HippyControllerProps(name = NodeProps.BACKGROUND_POSITION_Y, defaultType = HippyControllerProps.NUMBER)
public void setBackgroundImagePositionY(HippyViewGroup hippyViewGroup, int positionY) {
hippyViewGroup.setImagePositionY((int) PixelUtil.dp2px(positionY));
}
public static Integer getViewZIndex(View view) {
return mZIndexHash.get(view);
}
@Override
protected View createViewImpl(Context context) {
return new HippyViewGroup(context);
}
@HippyControllerProps(name = NodeProps.OVERFLOW, defaultType = HippyControllerProps.STRING,
defaultString = "visible")
public void setOverflow(HippyViewGroup hippyViewGroup, String overflow) {
hippyViewGroup.setOverflow(overflow);
}
@HippyControllerProps(name = NodeProps.BACKGROUND_IMAGE, defaultType = HippyControllerProps.STRING)
public void setBackgroundImage(HippyViewGroup hippyViewGroup, String url) {
hippyViewGroup.setUrl(getInnerPath((NativeRenderContext) hippyViewGroup.getContext(), url));
}
@HippyControllerProps(name = NodeProps.BACKGROUND_SIZE, defaultType = HippyControllerProps.STRING,
defaultString = "origin")
public void setBackgroundImageSize(HippyImageView hippyImageView, String resizeModeValue) {
if ("contain".equals(resizeModeValue)) {
// 在保持图片宽高比的前提下缩放图片,直到宽度和高度都小于等于容器视图的尺寸
// 这样图片完全被包裹在容器中,容器中可能留有空白
hippyImageView.setScaleType(HippyImageView.ScaleType.CENTER_INSIDE);
} else if ("cover".equals(resizeModeValue)) {
// 在保持图片宽高比的前提下缩放图片,直到宽度和高度都大于等于容器视图的尺寸
// 这样图片完全覆盖甚至超出容器,容器中不留任何空白
hippyImageView.setScaleType(HippyImageView.ScaleType.CENTER_CROP);
} else if ("center".equals(resizeModeValue)) {
// 居中不拉伸
hippyImageView.setScaleType(HippyImageView.ScaleType.CENTER);
} else if ("origin".equals(resizeModeValue)) {
// 不拉伸,居左上
hippyImageView.setScaleType(HippyImageView.ScaleType.ORIGIN);
} else {
// stretch and other mode
// 拉伸图片且不维持宽高比,直到宽高都刚好填满容器
hippyImageView.setScaleType(HippyImageView.ScaleType.FIT_XY);
}
}
@HippyControllerProps(name = NodeProps.BACKGROUND_POSITION_X, defaultType = HippyControllerProps.NUMBER)
public void setBackgroundImagePositionX(HippyViewGroup hippyViewGroup, int positionX) {
hippyViewGroup.setImagePositionX((int) PixelUtil.dp2px(positionX));
}
@HippyControllerProps(name = NodeProps.BACKGROUND_POSITION_Y, defaultType = HippyControllerProps.NUMBER)
public void setBackgroundImagePositionY(HippyViewGroup hippyViewGroup, int positionY) {
hippyViewGroup.setImagePositionY((int) PixelUtil.dp2px(positionY));
}
@SuppressWarnings("rawtypes")
@HippyControllerProps(name = NodeProps.BACKGROUND_RIPPLE, defaultType = HippyControllerProps.MAP)
public void setNativeBackground(HippyViewGroup hippyViewGroup, Map params) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !params.isEmpty()) {
Drawable drawable = DrawableFactory.createDrawable(DRAWABLE_TYPE_RIPPLE, params);
if (drawable != null) {
hippyViewGroup.setRippleDrawable(drawable);
}
}
}
@SuppressWarnings("rawtypes")
@Override
public void dispatchFunction(@NonNull HippyViewGroup viewGroup, @NonNull String functionName,
@NonNull List params) {
super.dispatchFunction(viewGroup, functionName, params);
switch (functionName) {
case FUNC_SET_PRESSED: {
boolean pressed = ArrayUtils.getBooleanValue(params, 0);
viewGroup.setPressed(pressed);
break;
}
case FUNC_SET_HOTSPOT: {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
float x = ArrayUtils.getFloatValue(params, 0);
float y = ArrayUtils.getFloatValue(params, 1);
viewGroup.drawableHotspotChanged(PixelUtil.dp2px(x), PixelUtil.dp2px(y));
}
break;
}
default:
break;
}
}
}
@@ -30,10 +30,15 @@ import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
/**
@@ -64,6 +69,7 @@ public class AsyncImageView extends ViewGroup implements Animator.AnimatorListen
protected int mTintColor;
protected ScaleType mScaleType;
protected Drawable mContentDrawable;
protected Drawable mRippleDrawable;
private boolean mIsAttached;
protected ImageLoaderAdapter mImageAdapter;
@@ -375,27 +381,9 @@ public class AsyncImageView extends ViewGroup implements Animator.AnimatorListen
if (!shouldSetContent()) {
return;
}
onSetContent(mUrl);
updateContentDrawableProperty(sourceType);
if (mBGDrawable != null) {
if (mContentDrawable instanceof ContentDrawable) {
((ContentDrawable) mContentDrawable)
.setBorder(mBGDrawable.getBorderRadiusArray(),
mBGDrawable.getBorderWidthArray());
((ContentDrawable) mContentDrawable)
.setShadowOffsetX(mBGDrawable.getShadowOffsetX());
((ContentDrawable) mContentDrawable)
.setShadowOffsetY(mBGDrawable.getShadowOffsetY());
((ContentDrawable) mContentDrawable)
.setShadowRadius(mBGDrawable.getShadowRadius());
}
setBackgroundDrawable(
new LayerDrawable(new Drawable[]{mBGDrawable, mContentDrawable}));
} else {
setBackgroundDrawable(mContentDrawable);
}
resetBackgroundDrawable();
afterSetContent(mUrl);
}
}
@@ -404,13 +392,11 @@ public class AsyncImageView extends ViewGroup implements Animator.AnimatorListen
if (!(mContentDrawable instanceof ContentDrawable)) {
return;
}
Bitmap bitmap = getBitmap();
if (sourceType == SOURCE_TYPE_DEFAULT_SRC && mDefaultSourceDrawable != null
&& (mUrlFetchState != IMAGE_LOADED || mSourceDrawable == null)) {
bitmap = mDefaultSourceDrawable.getBitmap();
}
if (bitmap != null) {
((ContentDrawable) mContentDrawable).setSourceType(sourceType);
((ContentDrawable) mContentDrawable).setBitmap(bitmap);
@@ -419,6 +405,17 @@ public class AsyncImageView extends ViewGroup implements Animator.AnimatorListen
((ContentDrawable) mContentDrawable).setImagePositionX(mImagePositionX);
((ContentDrawable) mContentDrawable).setImagePositionY(mImagePositionY);
}
if (mBGDrawable != null) {
((ContentDrawable) mContentDrawable)
.setBorder(mBGDrawable.getBorderRadiusArray(),
mBGDrawable.getBorderWidthArray());
((ContentDrawable) mContentDrawable)
.setShadowOffsetX(mBGDrawable.getShadowOffsetX());
((ContentDrawable) mContentDrawable)
.setShadowOffsetY(mBGDrawable.getShadowOffsetY());
((ContentDrawable) mContentDrawable)
.setShadowRadius(mBGDrawable.getShadowRadius());
}
}
protected void handleGetImageStart() {
@@ -587,4 +584,34 @@ public class AsyncImageView extends ViewGroup implements Animator.AnimatorListen
}
return mBGDrawable;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void setRippleDrawable(@NonNull Drawable rippleDrawable) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mRippleDrawable = rippleDrawable;
resetBackgroundDrawable();
}
}
private void resetBackgroundDrawable() {
ArrayList<Drawable> drawableList = new ArrayList<>();
if (mBGDrawable != null) {
drawableList.add(mBGDrawable);
}
if (mContentDrawable != null) {
drawableList.add(mContentDrawable);
}
if (mRippleDrawable != null) {
drawableList.add(mRippleDrawable);
}
if (drawableList.size() > 0) {
super.setBackground(null);
Drawable[] drawables = new Drawable[drawableList.size()];
for (int i = 0; i < drawableList.size(); i++) {
drawables[i] = drawableList.get(i);
}
LayerDrawable layerDrawable = new LayerDrawable(drawables);
super.setBackground(layerDrawable);
}
}
}
@@ -0,0 +1,88 @@
/* Tencent is pleased to support the open source community by making Hippy available.
* Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* 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.tencent.renderer.component.drawable;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
import com.tencent.mtt.hippy.utils.PixelUtil;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.util.Map;
public class DrawableFactory {
private static final String PROPERTY_RIPPLE_COLOR = "color";
private static final String PROPERTY_RIPPLE_RADIUS = "rippleRadius";
private static final String PROPERTY_RIPPLE_BORDERLESS = "borderless";
public enum DrawableType {
DRAWABLE_TYPE_RIPPLE
}
@Nullable
public static Drawable createDrawable(DrawableType type, @Nullable Map params) {
Drawable drawable = null;
switch (type) {
case DRAWABLE_TYPE_RIPPLE:
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
drawable = createRippleDrawable(params);
}
break;
default:
}
return drawable;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@NonNull
private static Drawable createRippleDrawable(@Nullable Map params) {
int color = Color.BLUE;
int radius = 0;
Drawable mask = null;
if (params != null) {
Object value = params.get(PROPERTY_RIPPLE_COLOR);
if (value instanceof Number) {
color = ((Number) value).intValue();
}
value = params.get(PROPERTY_RIPPLE_RADIUS);
if (value instanceof Number) {
double rd = ((Number) value).doubleValue();
radius = (int) (PixelUtil.dp2px(rd) + 0.5);
}
value = params.get(PROPERTY_RIPPLE_BORDERLESS);
if (value == null || (value instanceof Boolean && !((boolean) value))) {
mask = new ColorDrawable(Color.WHITE);
}
}
ColorStateList colorStateList =
new ColorStateList(new int[][]{new int[]{}}, new int[]{color});
RippleDrawable drawable = new RippleDrawable(colorStateList, null, mask);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && radius > 0) {
drawable.setRadius(radius);
}
return drawable;
}
}
@@ -36,6 +36,17 @@ public class ArrayUtils {
return DEFAULT_NUMBER_VALUE;
}
public static float getFloatValue(@NonNull List<?> params, int index) {
if (index >= params.size()) {
return DEFAULT_NUMBER_VALUE;
}
Object element = params.get(index);
if (element instanceof Number) {
return ((Number) element).floatValue();
}
return DEFAULT_NUMBER_VALUE;
}
public static double getDoubleValue(@NonNull List<?> params, int index) {
if (index >= params.size()) {
return DEFAULT_NUMBER_VALUE;
@@ -75,7 +75,7 @@ public class PropertyUtils {
return result;
}
if (paramCls == boolean.class || paramCls == Boolean.class || paramCls == HashMap.class
|| paramCls == ArrayList.class) {
|| paramCls == ArrayList.class || paramCls == Map.class || paramCls == List.class) {
return value;
}
// Although HippyMap and HippyArray has been deprecated, we need to be compatible with