| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.weex.utils; |
| |
| import android.annotation.SuppressLint; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.Paint; |
| import android.graphics.Path; |
| import android.graphics.PixelFormat; |
| import android.graphics.Point; |
| import android.graphics.RectF; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.graphics.drawable.ColorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.graphics.drawable.LayerDrawable; |
| import android.os.Build; |
| import android.os.Build.VERSION_CODES; |
| import android.support.annotation.IntDef; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.Nullable; |
| import android.util.DisplayMetrics; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.ViewGroup.LayoutParams; |
| import android.view.WindowManager; |
| |
| import org.apache.weex.WXEnvironment; |
| import org.apache.weex.WXSDKInstance; |
| import org.apache.weex.WXSDKManager; |
| import org.apache.weex.common.Constants; |
| import org.apache.weex.common.WXErrorCode; |
| import org.apache.weex.common.WXRuntimeException; |
| import org.apache.weex.ui.component.WXComponent; |
| import org.apache.weex.ui.flat.widget.Widget; |
| import org.apache.weex.ui.flat.widget.WidgetGroup; |
| import org.apache.weex.ui.view.border.BorderDrawable; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * Utility class for views |
| */ |
| public class WXViewUtils { |
| |
| @IntDef({PixelFormat.TRANSLUCENT, PixelFormat.TRANSPARENT, PixelFormat.OPAQUE}) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface Opacity {} |
| |
| /** |
| * Use {@link PixelFormat#TRANSLUCENT} instead |
| */ |
| @Deprecated |
| public static final int TRANSLUCENT = PixelFormat.TRANSLUCENT; |
| |
| /** |
| * Use {@link PixelFormat#TRANSPARENT} instead. |
| */ |
| @Deprecated |
| public static final int TRANSPARENT = PixelFormat.TRANSPARENT; |
| |
| /** |
| * Use {@link PixelFormat#OPAQUE} instead |
| */ |
| @Deprecated |
| public static final int OPAQUE = PixelFormat.OPAQUE; |
| |
| public static final int DIMENSION_UNSET = -1; |
| private static final boolean mUseWebPx = false; |
| |
| private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); |
| |
| @SuppressLint("NewApi") |
| public static int generateViewId() { |
| |
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { |
| for (;;) { |
| final int result = sNextGeneratedId.get(); |
| // aapt-generated IDs have the high byte nonzero; clamp to the range under that. |
| int newValue = result + 1; |
| if (newValue > 0x00FFFFFF) |
| newValue = 1; // Roll over to 1, not 0. |
| if (sNextGeneratedId.compareAndSet(result, newValue)) { |
| return result; |
| } |
| } |
| } else { |
| return View.generateViewId(); |
| } |
| } |
| |
| private static int mScreenWidth; |
| private static int mScreenHeight; |
| |
| |
| public static int getWeexHeight(String instanceId){ |
| WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(instanceId); |
| if (instance != null) { |
| int weexHeight = instance.getWeexHeight(); |
| if (weexHeight >= 0 || weexHeight == -2) { |
| return weexHeight; |
| } |
| else { |
| return getScreenHeight(WXEnvironment.sApplication); |
| } |
| } |
| return -3; |
| } |
| |
| |
| |
| public static int getWeexWidth(String instanceId){ |
| WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(instanceId); |
| if (instance != null) { |
| int weexWidth = instance.getWeexWidth(); |
| if (weexWidth >= 0 || weexWidth == -2) { |
| return weexWidth; |
| } |
| else { |
| return getScreenWidth(WXEnvironment.sApplication); |
| } |
| } |
| return -3; |
| } |
| |
| @Deprecated |
| public static int getScreenWidth( ) { |
| return getScreenWidth(WXEnvironment.sApplication); |
| } |
| |
| @Deprecated |
| public static int setScreenWidth(int screenWidth) { |
| return mScreenWidth = screenWidth; |
| } |
| |
| public static float getScreenDensity(Context ctx){ |
| if(ctx != null){ |
| try{ |
| Resources res = ctx.getResources(); |
| return res.getDisplayMetrics().density; |
| }catch (Exception e){ |
| WXLogUtils.e("getScreenDensityDpi exception:"+e.getMessage()); |
| } |
| } |
| return Constants.Value.DENSITY; |
| } |
| |
| public static void updateApplicationScreen(Context context){ |
| if(context == null || WXEnvironment.sApplication == null){ |
| return; |
| } |
| DisplayMetrics metrics = context.getResources().getDisplayMetrics(); |
| DisplayMetrics displayMetrics = WXEnvironment.sApplication.getResources().getDisplayMetrics(); |
| displayMetrics.heightPixels = metrics.heightPixels; |
| displayMetrics.widthPixels = metrics.widthPixels; |
| displayMetrics.density = metrics.density; |
| displayMetrics.densityDpi = metrics.densityDpi; |
| displayMetrics.scaledDensity = metrics.scaledDensity; |
| displayMetrics.xdpi = metrics.xdpi; |
| } |
| |
| public static int getScreenWidth(Context ctx) { |
| if(ctx!=null){ |
| Resources res = ctx.getResources(); |
| mScreenWidth = res.getDisplayMetrics().widthPixels; |
| if(WXEnvironment.SETTING_FORCE_VERTICAL_SCREEN){ |
| mScreenHeight = res |
| .getDisplayMetrics() |
| .heightPixels; |
| mScreenWidth = mScreenHeight > mScreenWidth ? mScreenWidth : mScreenHeight; |
| } |
| } else if(WXEnvironment.isApkDebugable()){ |
| throw new WXRuntimeException("Error Context is null When getScreenHeight"); |
| } |
| return mScreenWidth; |
| } |
| |
| |
| public static int getStatusBarHeight(Context context){ |
| Resources resources = context.getResources(); |
| int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android"); |
| if (resourceId > 0) { |
| int statusBarHeight = resources.getDimensionPixelSize(resourceId); |
| return statusBarHeight; |
| } |
| return -1; |
| } |
| |
| |
| @Deprecated |
| public static int getScreenHeight() { |
| return getScreenHeight(WXEnvironment.sApplication); |
| } |
| |
| |
| public static int getScreenHeight(String instanceId){ |
| WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(instanceId); |
| return instance.isFullScreenHeightEnabled()?getFullScreenHeight(WXEnvironment.sApplication):getScreenHeight(WXEnvironment.sApplication); |
| } |
| |
| //get screen height with status bar on full screen |
| public static int getFullScreenHeight(Context cxt) { |
| if(cxt!=null){ |
| WindowManager wm; |
| Resources res = cxt.getResources(); |
| if(Build.VERSION.SDK_INT >= 17 && (wm = (WindowManager)cxt.getSystemService(Context.WINDOW_SERVICE)) != null |
| && wm.getDefaultDisplay() != null){ |
| Point size = new Point(); |
| wm.getDefaultDisplay().getRealSize(size); |
| mScreenHeight = size.y; |
| } |
| else { |
| mScreenHeight = cxt.getResources().getDisplayMetrics().heightPixels; |
| } |
| if(WXEnvironment.SETTING_FORCE_VERTICAL_SCREEN){ |
| mScreenWidth = res |
| .getDisplayMetrics() |
| .widthPixels; |
| mScreenHeight = mScreenHeight > mScreenWidth ? mScreenHeight : mScreenWidth; |
| } |
| } else if (WXEnvironment.isApkDebugable()){ |
| throw new WXRuntimeException("Error Context is null When getScreenHeight"); |
| } |
| return mScreenHeight; |
| } |
| // get screen height without status bar |
| public static int getScreenHeight(Context cxt) { |
| if(cxt!=null){ |
| Resources res = cxt.getResources(); |
| mScreenHeight = res.getDisplayMetrics().heightPixels; |
| if(WXEnvironment.SETTING_FORCE_VERTICAL_SCREEN){ |
| mScreenWidth = res |
| .getDisplayMetrics() |
| .widthPixels; |
| mScreenHeight = mScreenHeight > mScreenWidth ? mScreenHeight : mScreenWidth; |
| } |
| } else if (WXEnvironment.isApkDebugable()){ |
| throw new WXRuntimeException("Error Context is null When getScreenHeight"); |
| } |
| return mScreenHeight; |
| } |
| |
| |
| /** |
| * Convert distance from JS,CSS to native. As the JS considers the width of the screen is 750px. |
| * There must be a transform when accessing distance from JS,CSS and use it. |
| * Basically, this method calculates a scale factor(ScreenWidth/750) and use apply this scale |
| * factor to JS,CSS distance. |
| * @param pxValue the raw distance from JS or CSS. The result will be rounded to a closet int. |
| * @return the actual distance in the screen. |
| */ |
| |
| @Deprecated |
| public static float getRealPxByWidth(float pxValue) { |
| return getRealPxByWidth(pxValue,750); |
| } |
| public static float getRealPxByWidth(float pxValue,int customViewport) { |
| if (Float.isNaN(pxValue)) { |
| return pxValue; |
| } |
| if (mUseWebPx) { |
| return (float) Math.rint(pxValue); |
| } else { |
| float realPx = (pxValue * getScreenWidth() / customViewport); |
| return realPx > 0.005 && realPx < 1 ? 1 : (float) Math.rint(realPx); |
| } |
| } |
| |
| @Deprecated |
| public static float getRealSubPxByWidth(float pxValue) { |
| return getRealSubPxByWidth(pxValue,750); |
| } |
| |
| public static float getRealSubPxByWidth(float pxValue,int customViewport) { |
| if (Float.isNaN(pxValue)) { |
| return pxValue; |
| } |
| if (mUseWebPx) { |
| return (float) Math.rint(pxValue); |
| } else { |
| float realPx = (pxValue * getScreenWidth() / customViewport); |
| return realPx > 0.005 && realPx < 1 ? 1 : realPx; |
| } |
| } |
| |
| /** |
| * Internal interface that just for debug, you should never call this method because of accuracy loss obviously |
| */ |
| @Deprecated |
| public static float getWeexPxByReal(float pxValue) { |
| return getWeexPxByReal(pxValue,750); |
| } |
| |
| public static float getWeexPxByReal(float pxValue,int customViewport) { |
| if (Float.isNaN(pxValue)) { |
| return pxValue; |
| } |
| if (mUseWebPx) { |
| return (float) Math.rint(pxValue); |
| } else { |
| return pxValue * customViewport / getScreenWidth(); |
| } |
| } |
| |
| @Deprecated |
| public static float getRealPxByWidth2(float pxValue) { |
| return getRealPxByWidth2(pxValue,750); |
| } |
| public static int getRealPxByWidth2(float pxValue,int customViewport) { |
| if (mUseWebPx) { |
| return (int) pxValue; |
| } else { |
| float realPx = (pxValue * getScreenWidth() / customViewport); |
| return realPx > 0.005 && realPx < 1 ? 1 : (int) realPx - 1; |
| } |
| } |
| |
| /** |
| * Convert distance from native to JS,CSS. As the JS considers the width of the screen is 750px. |
| * There must be a transform when return distance to JS,CSS. |
| * Basically, this method calculates a scale factor(ScreenWidth/750) and use apply this scale |
| * factor to native distance. |
| * @param pxValue the raw distance of native. The result will be rounded to a closet int. |
| * @return the distance in JS,CSS where the screenWidth is 750 px. |
| */ |
| @Deprecated |
| public static float getWebPxByWidth(float pxValue) { |
| return getWebPxByWidth(pxValue,750); |
| } |
| |
| public static float getWebPxByWidth(float pxValue,int customViewport) { |
| if (pxValue < -1.9999 && pxValue > -2.005) { |
| return Float.NaN; |
| } |
| if (mUseWebPx) { |
| return pxValue; |
| } else { |
| float realPx = (pxValue * customViewport / getScreenWidth()); |
| return realPx > 0.005 && realPx < 1 ? 1 : realPx; |
| } |
| } |
| |
| |
| /** |
| * Convert dp to px |
| * @param dpValue the dp value to be converted |
| * @return the px value |
| */ |
| public static int dip2px(float dpValue) { |
| float scale = 2; |
| try { |
| scale = WXEnvironment.getApplication().getResources() |
| .getDisplayMetrics().density; |
| } catch (Exception e) { |
| WXLogUtils.e("[WXViewUtils] dip2px:", e); |
| } |
| float finalPx = (dpValue * scale + 0.5f); |
| return finalPx > 0 && finalPx < 1 ? 1 : (int) finalPx; |
| } |
| |
| public static boolean onScreenArea(View view) { |
| if (view == null || view.getVisibility() != View.VISIBLE) { |
| return false; |
| } |
| |
| int[] p = new int[2]; |
| view.getLocationOnScreen(p); |
| LayoutParams lp = view.getLayoutParams(); |
| int viewH = 0; |
| if (lp != null) { |
| viewH = lp.height; |
| } else { |
| viewH = view.getHeight(); |
| } |
| |
| return (p[1] > 0 && (p[1] - WXViewUtils.getScreenHeight(WXEnvironment.sApplication) < 0)) |
| || (viewH + p[1] > 0 && p[1] <= 0); |
| } |
| |
| /** |
| * Multiplies the color with the given alpha. |
| * |
| * @param color color to be multiplied |
| * @param alpha value between 0 and 255 |
| * @return multiplied color |
| */ |
| public static int multiplyColorAlpha(int color, int alpha) { |
| if (alpha == 255) { |
| return color; |
| } |
| if (alpha == 0) { |
| return color & 0x00FFFFFF; |
| } |
| alpha = alpha + (alpha >> 7); // make it 0..256 |
| int colorAlpha = color >>> 24; |
| int multipliedAlpha = colorAlpha * alpha >> 8; |
| return (multipliedAlpha << 24) | (color & 0x00FFFFFF); |
| } |
| |
| @Opacity |
| public static int getOpacityFromColor(int color) { |
| int colorAlpha = color >>> 24; |
| if (colorAlpha == 255) { |
| return PixelFormat.OPAQUE; |
| } else if (colorAlpha == 0) { |
| return PixelFormat.TRANSPARENT; |
| } else { |
| return PixelFormat.TRANSLUCENT; |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| public static void setBackGround(View view, Drawable drawable, WXComponent component) { |
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN){ |
| view.setBackgroundDrawable(drawable); |
| } |
| else{ |
| try { |
| view.setBackground(drawable); |
| } catch (Exception e) { |
| if (component == null) |
| return; |
| WXExceptionUtils.commitCriticalExceptionRT(component.getInstanceId(), |
| WXErrorCode.WX_RENDER_ERR_TEXTURE_SETBACKGROUND, |
| component.getComponentType() + " setBackGround for android view", |
| WXErrorCode.WX_RENDER_ERR_TEXTURE_SETBACKGROUND.getErrorMsg() + ": TextureView doesn't support displaying a background drawable!", |
| null); |
| } |
| } |
| } |
| |
| public static @Nullable |
| BorderDrawable getBorderDrawable(@NonNull View view){ |
| Drawable drawable=view.getBackground(); |
| if(drawable instanceof BorderDrawable){ |
| return (BorderDrawable) drawable; |
| } |
| else if(drawable instanceof LayerDrawable){ |
| if(((LayerDrawable) drawable).getNumberOfLayers()>1) { |
| Drawable innerDrawable=((LayerDrawable) drawable).getDrawable(0); |
| if(innerDrawable instanceof BorderDrawable){ |
| return (BorderDrawable) innerDrawable; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public static void clipCanvasWithinBorderBox(View targetView, Canvas canvas) { |
| Drawable drawable; |
| if (clipCanvasDueToAndroidVersion(canvas) && |
| clipCanvasIfAnimationExist(targetView) && |
| ((drawable = targetView.getBackground()) instanceof BorderDrawable)) { |
| BorderDrawable borderDrawable = (BorderDrawable) drawable; |
| if (borderDrawable.isRounded()) { |
| if (clipCanvasIfBackgroundImageExist(targetView, borderDrawable)) { |
| Path path = borderDrawable.getContentPath( |
| new RectF(0, 0, targetView.getWidth(), targetView.getHeight())); |
| canvas.clipPath(path); |
| } |
| } |
| } |
| } |
| |
| public static void clipCanvasWithinBorderBox(Widget widget, Canvas canvas) { |
| BorderDrawable borderDrawable; |
| if (clipCanvasDueToAndroidVersion(canvas) && |
| clipCanvasIfAnimationExist(null) && |
| (borderDrawable=widget.getBackgroundAndBorder())!=null ) { |
| if (borderDrawable.isRounded() && clipCanvasIfBackgroundImageExist(widget, borderDrawable)) { |
| Path path = borderDrawable.getContentPath( |
| new RectF(0, 0, widget.getBorderBox().width(), widget.getBorderBox().height())); |
| canvas.clipPath(path); |
| } |
| else { |
| canvas.clipRect(widget.getBorderBox()); |
| } |
| } |
| } |
| |
| /** |
| * According to https://developer.android.com/guide/topics/graphics/hardware-accel.html#unsupported |
| API 18 or higher supports clipPath to canvas based on hardware acceleration. |
| * @param canvas |
| * @return |
| */ |
| private static boolean clipCanvasDueToAndroidVersion(Canvas canvas) { |
| return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 || |
| !canvas.isHardwareAccelerated(); |
| } |
| |
| /** |
| * According to https://code.google.com/p/android/issues/detail?id=225556&sort=-id&colspec=ID |
| * clipPath doesn't work with rotation nor scale when API level is 24. |
| * As animation will not cause redraw if hardware-acceleration enabled, clipCanvas feature has |
| * to be disabled when API level is 24 without considering the animation property. |
| */ |
| private static boolean clipCanvasIfAnimationExist(View targetView) { |
| if (Build.VERSION.SDK_INT != VERSION_CODES.N) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * Due limitation in Android platform, the linear gradient in the following page will not be |
| * rounded if {@link Canvas#clipPath(Path)} of the parent view invoked when API level is lower |
| * than 21. |
| * http://dotwe.org/weex/963c9ade129f86757cecdd85651cd30e |
| * @param targetView |
| * @param borderDrawable |
| * @return |
| */ |
| private static boolean clipCanvasIfBackgroundImageExist(@NonNull View targetView, |
| @NonNull BorderDrawable borderDrawable) { |
| if (targetView instanceof ViewGroup) { |
| View child; |
| ViewGroup parent = ((ViewGroup) targetView); |
| int count = parent.getChildCount(); |
| for (int i = 0; i < count; i++) { |
| child = parent.getChildAt(i); |
| if (child.getBackground() instanceof BorderDrawable && |
| ((BorderDrawable) child.getBackground()).hasImage() && |
| Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| private static boolean clipCanvasIfBackgroundImageExist(@NonNull Widget widget, |
| @NonNull BorderDrawable borderDrawable) { |
| if (widget instanceof WidgetGroup) { |
| for (Widget child : ((WidgetGroup) widget).getChildren()) { |
| if (child.getBackgroundAndBorder().hasImage() && |
| Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| public static boolean isViewVisible(View v) { |
| if (null == v){ |
| return false; |
| } |
| |
| boolean isAttachToWindow = Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT |
| ?v.isAttachedToWindow() |
| :v.getWindowToken() != null; |
| |
| if (!isAttachToWindow){ |
| return false; |
| } |
| if (v.getVisibility() != View.VISIBLE || v.getAlpha()<=0){ |
| return false; |
| } |
| |
| Drawable bacDrawable = v.getBackground(); |
| if (null == bacDrawable){ |
| return true; |
| } |
| if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT){ |
| return bacDrawable.getAlpha()>0; |
| } |
| //< 4.4 |
| if (bacDrawable instanceof ColorDrawable){ |
| int alpha = Color.alpha(((ColorDrawable) bacDrawable).getColor()); |
| return alpha >0; |
| }else if (bacDrawable instanceof BitmapDrawable){ |
| Paint paint = ((BitmapDrawable) bacDrawable).getPaint(); |
| return paint.getAlpha() > 0; |
| } |
| return true; |
| } |
| } |