[ https://issues.apache.org/jira/browse/WEEX-653?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16687697#comment-16687697 ]
ASF GitHub Bot commented on WEEX-653: ------------------------------------- YorkShen closed pull request #1664: [WEEX-653][android][iOS][core] Total platform support rtl direction by CSS "direction:rtl" URL: https://github.com/apache/incubator-weex/pull/1664 This is a PR merged from a forked repository. As GitHub hides the original diff on merge, it is displayed below for the sake of provenance: As this is a foreign pull request (from a fork), the diff is supplied below (as it won't show otherwise due to GitHub magic): diff --git a/android/playground/app/src/main/java/com/alibaba/weex/extend/component/WXMask.java b/android/playground/app/src/main/java/com/alibaba/weex/extend/component/WXMask.java index 778404c3cc..252de1c56f 100644 --- a/android/playground/app/src/main/java/com/alibaba/weex/extend/component/WXMask.java +++ b/android/playground/app/src/main/java/com/alibaba/weex/extend/component/WXMask.java @@ -124,7 +124,9 @@ protected void setHostLayoutParams(View host, int width, int height, int left, i top = get(TOP); bottom = get(BOTTOM); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, height); - params.setMargins(left, top, right, bottom); + + this.setMarginsSupportRTL(params, left, top, right, bottom); + getHostView().setLayoutParams(params); } diff --git a/android/sdk/libs/armeabi-v7a/libweexcore.so b/android/sdk/libs/armeabi-v7a/libweexcore.so old mode 100755 new mode 100644 index 08a3751a9c..4b1cac43ff Binary files a/android/sdk/libs/armeabi-v7a/libweexcore.so and b/android/sdk/libs/armeabi-v7a/libweexcore.so differ diff --git a/android/sdk/libs/armeabi/libweexcore.so b/android/sdk/libs/armeabi/libweexcore.so old mode 100755 new mode 100644 index a88a0fffb8..1be8d2be56 Binary files a/android/sdk/libs/armeabi/libweexcore.so and b/android/sdk/libs/armeabi/libweexcore.so differ diff --git a/android/sdk/libs/x86/libJavaScriptCore.so b/android/sdk/libs/x86/libJavaScriptCore.so new file mode 100644 index 0000000000..0e0f9f5ed7 Binary files /dev/null and b/android/sdk/libs/x86/libJavaScriptCore.so differ diff --git a/android/sdk/libs/x86/libweexcore.so b/android/sdk/libs/x86/libweexcore.so old mode 100755 new mode 100644 index 9955dc3fca..e4c3a3b8fa Binary files a/android/sdk/libs/x86/libweexcore.so and b/android/sdk/libs/x86/libweexcore.so differ diff --git a/android/sdk/libs/x86/libweexjss.so b/android/sdk/libs/x86/libweexjss.so old mode 100755 new mode 100644 index ac0dd7f261..3733a2a383 Binary files a/android/sdk/libs/x86/libweexjss.so and b/android/sdk/libs/x86/libweexjss.so differ diff --git a/android/sdk/libs/x86/libweexjst.so b/android/sdk/libs/x86/libweexjst.so new file mode 100644 index 0000000000..84add1ebda Binary files /dev/null and b/android/sdk/libs/x86/libweexjst.so differ diff --git a/android/sdk/src/main/java/com/taobao/weex/WXEnvironment.java b/android/sdk/src/main/java/com/taobao/weex/WXEnvironment.java index 13564ff86b..cf4c7b2adb 100644 --- a/android/sdk/src/main/java/com/taobao/weex/WXEnvironment.java +++ b/android/sdk/src/main/java/com/taobao/weex/WXEnvironment.java @@ -157,6 +157,13 @@ configs.put(WXConfig.sysModel, SYS_MODEL); configs.put(WXConfig.weexVersion, String.valueOf(WXSDK_VERSION)); configs.put(WXConfig.logLevel,sLogLevel.getName()); + + try { + configs.put(WXConfig.layoutDirection, isLayoutDirectionRTL() ? "rtl" : "ltr"); + } catch (Exception e) { + configs.put(WXConfig.layoutDirection, "ltr"); + } + try { if (isApkDebugable()) { options.put(WXConfig.debugMode, "true"); @@ -225,6 +232,13 @@ public static boolean isSupport() { return isHardwareSupport() && isInitialized; } + public static boolean isLayoutDirectionRTL() { + // support RTL + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { + return sApplication.getApplicationContext().getResources().getBoolean(R.bool.weex_is_right_to_left); + } + return false; + } /** * Tell whether Weex can run on current hardware. * @return true if weex can run on current hardware, otherwise false. diff --git a/android/sdk/src/main/java/com/taobao/weex/WXSDKInstance.java b/android/sdk/src/main/java/com/taobao/weex/WXSDKInstance.java index cfa87b7e66..0962db545f 100644 --- a/android/sdk/src/main/java/com/taobao/weex/WXSDKInstance.java +++ b/android/sdk/src/main/java/com/taobao/weex/WXSDKInstance.java @@ -1664,6 +1664,13 @@ public int getRenderContainerPaddingLeft() { return 0; } + public int getRenderContainerPaddingRight() { + if(mRenderContainer != null) { + return mRenderContainer.getPaddingRight(); + } + return 0; + } + public int getRenderContainerPaddingTop() { if(mRenderContainer != null) { return mRenderContainer.getPaddingTop(); diff --git a/android/sdk/src/main/java/com/taobao/weex/bridge/WXBridgeManager.java b/android/sdk/src/main/java/com/taobao/weex/bridge/WXBridgeManager.java index 531b601133..cbe68aebff 100644 --- a/android/sdk/src/main/java/com/taobao/weex/bridge/WXBridgeManager.java +++ b/android/sdk/src/main/java/com/taobao/weex/bridge/WXBridgeManager.java @@ -1975,6 +1975,7 @@ private WXParams assembleDefaultOptions() { wxParams.setDeviceModel(config.get(WXConfig.sysModel)); wxParams.setShouldInfoCollect(config.get("infoCollect")); wxParams.setLogLevel(config.get(WXConfig.logLevel)); + wxParams.setLayoutDirection(config.get(WXConfig.layoutDirection)); wxParams.setUseSingleProcess(isUseSingleProcess ? "true" : "false"); wxParams.setCrashFilePath(WXEnvironment.getCrashFilePath(WXEnvironment.getApplication().getApplicationContext())); wxParams.setLibJssPath(WXEnvironment.getLibJssRealPath()); diff --git a/android/sdk/src/main/java/com/taobao/weex/bridge/WXParams.java b/android/sdk/src/main/java/com/taobao/weex/bridge/WXParams.java index 8b41a8fae2..657b21aafd 100644 --- a/android/sdk/src/main/java/com/taobao/weex/bridge/WXParams.java +++ b/android/sdk/src/main/java/com/taobao/weex/bridge/WXParams.java @@ -42,6 +42,7 @@ private String useSingleProcess; private String crashFilePath; private String libJssPath; + private String layoutDirection; private String libJscPath; private String libIcuPath; @@ -120,6 +121,11 @@ public void setDeviceModel(String deviceModel) { this.deviceModel = deviceModel; } + @CalledByNative + public String getLayoutDirection() {return layoutDirection;} + + public void setLayoutDirection(String direction) { this.layoutDirection = direction; } + @CalledByNative public String getAppName() { return appName; @@ -246,6 +252,7 @@ public void setLibLdPath(String libLdPath) { map.put("deviceHeight", deviceHeight); map.put("deviceModel", deviceModel); map.put("deviceWidth", deviceWidth); + map.put("layoutDirection", layoutDirection); map.put("libJssPath", libJssPath); map.put("logLevel", logLevel); map.put("needInitV8", needInitV8); diff --git a/android/sdk/src/main/java/com/taobao/weex/common/WXConfig.java b/android/sdk/src/main/java/com/taobao/weex/common/WXConfig.java index 11dc82090c..7bf525c048 100644 --- a/android/sdk/src/main/java/com/taobao/weex/common/WXConfig.java +++ b/android/sdk/src/main/java/com/taobao/weex/common/WXConfig.java @@ -33,5 +33,6 @@ String externalUserAgent="externalUserAgent"; String logLevel="logLevel"; String scale = "scale"; + String layoutDirection = "layoutDirection"; String debugMode = "debugMode"; } diff --git a/android/sdk/src/main/java/com/taobao/weex/dom/WXStyle.java b/android/sdk/src/main/java/com/taobao/weex/dom/WXStyle.java index a401090370..17f549e64b 100644 --- a/android/sdk/src/main/java/com/taobao/weex/dom/WXStyle.java +++ b/android/sdk/src/main/java/com/taobao/weex/dom/WXStyle.java @@ -178,13 +178,17 @@ public static String getFontFamily(Map<String, Object> style) { } public static Layout.Alignment getTextAlignment(Map<String, Object> style){ - Layout.Alignment alignment= Layout.Alignment.ALIGN_NORMAL; + return getTextAlignment(style, false); + } + + public static Layout.Alignment getTextAlignment(Map<String, Object> style, boolean isRTL){ + Layout.Alignment alignment= isRTL ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_NORMAL; String textAlign= (String) style.get(Constants.Name.TEXT_ALIGN); if(TextUtils.equals(Constants.Value.LEFT,textAlign)){ alignment= Layout.Alignment.ALIGN_NORMAL; } else if(TextUtils.equals(Constants.Value.CENTER,textAlign)){ - alignment=Layout.Alignment.ALIGN_CENTER; + alignment= Layout.Alignment.ALIGN_CENTER; } else if(TextUtils.equals(Constants.Value.RIGHT,textAlign)){ alignment= Layout.Alignment.ALIGN_OPPOSITE; diff --git a/android/sdk/src/main/java/com/taobao/weex/layout/measurefunc/TextContentBoxMeasurement.java b/android/sdk/src/main/java/com/taobao/weex/layout/measurefunc/TextContentBoxMeasurement.java index 66d0faeb46..ef0ccb094a 100644 --- a/android/sdk/src/main/java/com/taobao/weex/layout/measurefunc/TextContentBoxMeasurement.java +++ b/android/sdk/src/main/java/com/taobao/weex/layout/measurefunc/TextContentBoxMeasurement.java @@ -232,7 +232,7 @@ private void updateStyleImp(Map<String, Object> style) { if (style.containsKey(Constants.Name.FONT_FAMILY)) { mFontFamily = WXStyle.getFontFamily(style); } - mAlignment = WXStyle.getTextAlignment(style); + mAlignment = WXStyle.getTextAlignment(style, mComponent.isNativeLayoutRTL()); textOverflow = WXStyle.getTextOverflow(style); int lineHeight = WXStyle.getLineHeight(style, mComponent.getViewPortWidth()); if (lineHeight != UNSET) { diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/action/ActionGetLayoutDirection.java b/android/sdk/src/main/java/com/taobao/weex/ui/action/ActionGetLayoutDirection.java new file mode 100644 index 0000000000..039bdfce08 --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/action/ActionGetLayoutDirection.java @@ -0,0 +1,117 @@ +/** + * 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 com.taobao.weex.ui.action; + +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.view.View; + +import com.taobao.weex.WXSDKInstance; +import com.taobao.weex.WXSDKManager; +import com.taobao.weex.bridge.JSCallback; +import com.taobao.weex.bridge.SimpleJSCallback; +import com.taobao.weex.ui.component.WXComponent; +import com.taobao.weex.ui.component.list.template.jni.NativeRenderObjectUtils; +import com.taobao.weex.utils.WXViewUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by listen on 18/09/12. + */ +public class ActionGetLayoutDirection extends BasicGraphicAction { + + private final String mCallback; + + public ActionGetLayoutDirection(WXSDKInstance instance, String ref, String callback) { + super(instance, ref); + this.mCallback = callback; + } + + @Override + public void executeAction() { + WXSDKInstance instance = getWXSDKIntance(); + if (instance == null || instance.isDestroy()) { + return; + } + + JSCallback jsCallback = new SimpleJSCallback(instance.getInstanceId(), mCallback); + + if (TextUtils.isEmpty(getRef())) { + Map<String, Object> options = new HashMap<>(); + options.put("result", false); + options.put("errMsg", "Illegal parameter"); + jsCallback.invoke(options); + } else if ("viewport".equalsIgnoreCase(getRef())) { + callbackViewport(instance, jsCallback); + } else { + WXComponent component = WXSDKManager.getInstance().getWXRenderManager().getWXComponent(getPageId(), getRef()); + if (component == null) { + return; + } + + String directionRet = "ltr"; + if (component != null) { + int direction = NativeRenderObjectUtils.nativeRenderObjectGetLayoutDirectionFromPathNode(component.getRenderObjectPtr()); + switch (direction) { + case 0: { + directionRet = "inherit"; + break; + } + case 1: { + directionRet = "ltr"; + break; + } + case 2: { + directionRet = "rtl"; + break; + } + default: { + directionRet = "ltr"; + break; + } + + } + } + jsCallback.invoke(directionRet); + } + } + + private void callbackViewport(WXSDKInstance instance, JSCallback jsCallback) { + View container; + if ((container = instance.getContainerView()) != null) { + Map<String, Object> options = new HashMap<>(); + options.put("direction", "ltr"); + options.put("result", true); + jsCallback.invoke(options); + } else { + Map<String, Object> options = new HashMap<>(); + options.put("result", false); + options.put("errMsg", "Component does not exist"); + jsCallback.invoke(options); + } + } + + @NonNull + private float getWebPxValue(int value,int viewport) { + return WXViewUtils.getWebPxByWidth(value, viewport); + } + +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/WXComponent.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/WXComponent.java index 7840b9a394..fbf8bc3279 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/component/WXComponent.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/WXComponent.java @@ -46,6 +46,7 @@ import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.text.TextUtils; import android.util.Pair; +import android.view.Gravity; import android.view.Menu; import android.view.View; import android.view.ViewGroup; @@ -87,6 +88,7 @@ import com.taobao.weex.ui.component.basic.WXBasicComponent; import com.taobao.weex.ui.component.binding.Statements; import com.taobao.weex.ui.component.list.WXCell; +import com.taobao.weex.ui.component.list.template.jni.NativeRenderLayoutDirection; import com.taobao.weex.ui.component.list.template.jni.NativeRenderObjectUtils; import com.taobao.weex.ui.component.pesudo.OnActivePseudoListner; import com.taobao.weex.ui.component.pesudo.PesudoStatus; @@ -153,6 +155,7 @@ private int mPreRealWidth = 0; private int mPreRealHeight = 0; private int mPreRealLeft = 0; + private int mPreRealRight = 0; private int mPreRealTop = 0; private int mStickyOffset = 0; protected WXGesture mGesture; @@ -233,6 +236,44 @@ protected final void setContentBoxMeasurement(final ContentBoxMeasurement conten WXBridgeManager.getInstance().bindMeasurementToRenderObject(getRenderObjectPtr()); } + + public void setMarginsSupportRTL(ViewGroup.MarginLayoutParams lp, int left, int top, int right, int bottom) { + lp.setMargins(left, top, right, bottom); + if (lp instanceof FrameLayout.LayoutParams) { + FrameLayout.LayoutParams lp_frameLayout = (FrameLayout.LayoutParams) lp; + lp_frameLayout.gravity = Gravity.LEFT | Gravity.TOP; + } + } + + public boolean isNativeLayoutRTL() { + return NativeRenderObjectUtils.nativeRenderObjectGetLayoutDirectionFromPathNode(this.getRenderObjectPtr()) == NativeRenderLayoutDirection.rtl; + } + + public static boolean isLayoutRTL(WXComponent cmp) { + if (cmp == null) return false; + + View view = cmp.getHostView(); + if (ViewCompat.isLayoutDirectionResolved(view)) { + return ViewCompat.getLayoutDirection(view) == View.LAYOUT_DIRECTION_RTL; + } else if (cmp.getParent() != null){ + return isLayoutRTL(cmp.getParent()); + } else { + return isLayoutRTL((ViewGroup) view.getParent()); + } + } + + public static boolean isLayoutRTL(ViewGroup viewGroup) { + if (viewGroup == null) return false; + + if (ViewCompat.isLayoutDirectionResolved(viewGroup)) { + return ViewCompat.getLayoutDirection(viewGroup) == View.LAYOUT_DIRECTION_RTL; + } else if (viewGroup.getParent() instanceof ViewGroup) { + return isLayoutRTL((ViewGroup) viewGroup.getParent()); + } else { + return false; + } + } + public void updateStyles(WXComponent component) { if (component != null) { updateProperties(component.getStyles()); @@ -881,8 +922,7 @@ protected BorderDrawable getOrCreateBorder() { /** * layout view */ - public final void setLayout(WXComponent component) { - + public void setLayout(WXComponent component) { if (TextUtils.isEmpty(component.getComponentType()) || TextUtils.isEmpty(component.getRef()) || component.getLayoutPosition() == null || component.getLayoutSize() == null) { @@ -910,6 +950,7 @@ public final void setLayout(WXComponent component) { int realLeft = 0; int realTop = 0; + int realRight = 0; if (isFixed()) { realLeft = (int) (getLayoutPosition().getLeft() - getInstance().getRenderContainerPaddingLeft()); @@ -921,14 +962,14 @@ public final void setLayout(WXComponent component) { parentPadding.get(CSSShorthand.EDGE.TOP) - parentBorder.get(CSSShorthand.EDGE.TOP)) + siblingOffset; } - int realRight = (int) getMargin().get(CSSShorthand.EDGE.RIGHT); + realRight = (int) getMargin().get(CSSShorthand.EDGE.RIGHT); int realBottom = (int) getMargin().get(CSSShorthand.EDGE.BOTTOM); Point rawOffset = new Point( (int) getLayoutPosition().getLeft(), (int) getLayoutPosition().getTop()); - if (mPreRealWidth == realWidth && mPreRealHeight == realHeight && mPreRealLeft == realLeft && mPreRealTop == realTop) { + if (mPreRealWidth == realWidth && mPreRealHeight == realHeight && mPreRealLeft == realLeft && mPreRealRight == realRight && mPreRealTop == realTop) { return; } @@ -994,6 +1035,7 @@ private void setComponentLayoutParams(int realWidth, int realHeight, int realLef mPreRealWidth = realWidth; mPreRealHeight = realHeight; mPreRealLeft = realLeft; + mPreRealRight = realRight; mPreRealTop = realTop; onFinishLayout(); // restore box shadow @@ -1047,14 +1089,14 @@ public int getLayoutTopOffsetForSibling() { protected void setHostLayoutParams(T host, int width, int height, int left, int right, int top, int bottom) { ViewGroup.LayoutParams lp; if (mParent == null) { - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, height); - params.setMargins(left, top, right, bottom); - lp = params; + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, height); + this.setMarginsSupportRTL(params, left, top, right, bottom); + lp = params; } else { - lp = mParent.getChildLayoutParams(this, host, width, height, left, right, top, bottom); + lp = mParent.getChildLayoutParams(this, host, width, height, left, right, top, bottom); } if (lp != null) { - host.setLayoutParams(lp); + host.setLayoutParams(lp); } } @@ -1063,7 +1105,9 @@ private void setFixedHostLayoutParams(T host, int width, int height, int left, i params.width = width; params.height = height; - params.setMargins(left, top, right, bottom); + + this.setMarginsSupportRTL(params, left, top, right, bottom); + host.setLayoutParams(params); mInstance.moveFixedView(host); @@ -1844,6 +1888,7 @@ public boolean isDestoryed() { public View detachViewAndClearPreInfo() { View original = mHost; mPreRealLeft = 0; + mPreRealRight = 0; mPreRealWidth = 0; mPreRealHeight = 0; mPreRealTop = 0; @@ -1853,6 +1898,7 @@ public View detachViewAndClearPreInfo() { public void clearPreLayout() { mPreRealLeft = 0; + mPreRealRight = 0; mPreRealWidth = 0; mPreRealHeight = 0; mPreRealTop = 0; diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/WXIndicator.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/WXIndicator.java index e8422cadd4..449c12ee25 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/component/WXIndicator.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/WXIndicator.java @@ -54,7 +54,7 @@ public WXIndicator(WXSDKInstance instance, WXVContainer parent, boolean isLazy, @Override protected void setHostLayoutParams(WXCircleIndicator host, int width, int height, int left, int right, int top, int bottom) { FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, height); - params.setMargins(left, top, right, bottom); + this.setMarginsSupportRTL(params, left, top, right, bottom); host.setLayoutParams(params); } diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/WXScroller.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/WXScroller.java index 81c0e177a0..bfef86ff94 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/component/WXScroller.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/WXScroller.java @@ -33,7 +33,11 @@ import android.os.Handler; import android.os.Looper; import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; +import android.support.v4.view.ViewCompat; import android.text.TextUtils; +import android.view.Gravity; +import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; @@ -377,6 +381,42 @@ public void destroy() { } } + @Override + public void setMarginsSupportRTL(ViewGroup.MarginLayoutParams lp, int left, int top, int right, int bottom) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { + lp.setMargins(left, top, right, bottom); + lp.setMarginStart(left); + lp.setMarginEnd(right); + } else { + if (lp instanceof FrameLayout.LayoutParams) { + FrameLayout.LayoutParams lp_frameLayout = (FrameLayout.LayoutParams) lp; + if (this.isNativeLayoutRTL()) { + lp_frameLayout.gravity = Gravity.RIGHT | Gravity.TOP; + lp.setMargins(right, top, left, bottom); + } else { + lp_frameLayout.gravity = Gravity.LEFT | Gravity.TOP; + lp.setMargins(left, top, right, bottom); + } + } else { + lp.setMargins(left, top, right, bottom); + } + } + } + + @Override + public void setLayout(WXComponent component) { + if (TextUtils.isEmpty(component.getComponentType()) + || TextUtils.isEmpty(component.getRef()) || component.getLayoutPosition() == null + || component.getLayoutSize() == null) { + return; + } + if (component.getHostView() != null) { + int layoutDirection = component.isNativeLayoutRTL() ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR; + ViewCompat.setLayoutDirection(component.getHostView(), layoutDirection); + } + super.setLayout(component); + } + @Override protected MeasureOutput measure(int width, int height) { MeasureOutput measureOutput = new MeasureOutput(); @@ -437,6 +477,37 @@ public void onScrollChanged(WXHorizontalScrollView scrollView, int x, int y, int scrollView.addView(mRealView, layoutParams); scrollView.setHorizontalScrollBarEnabled(false); + final WXScroller component = this; + final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + final View frameLayout = view; + scrollView.post(new Runnable() { + @Override + public void run() { + if (isNativeLayoutRTL()) { + int mw = frameLayout.getMeasuredWidth(); + scrollView.scrollTo(mw, component.getScrollY()); + } else { + scrollView.scrollTo(0, component.getScrollY()); + } + } + }); + } + }; + mRealView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View view) { + view.addOnLayoutChangeListener(listener); + } + + @Override + public void onViewDetachedFromWindow(View view) { + view.removeOnLayoutChangeListener(listener); + } + }); + + if(pageEnable) { mGestureDetector = new GestureDetector(new MyGestureDetector(scrollView)); scrollView.setOnTouchListener(new View.OnTouchListener() { @@ -706,9 +777,21 @@ public void scrollTo(WXComponent component, Map<String, Object> options) { mActiveFeature = mChildren.indexOf(component); } - - int viewYInScroller=component.getAbsoluteY() - getAbsoluteY(); - int viewXInScroller=component.getAbsoluteX() - getAbsoluteX(); + int viewYInScroller = component.getAbsoluteY() - getAbsoluteY(); + int viewXInScroller = 0; + if (this.isNativeLayoutRTL()) { + // if layout direction is rtl, we need calculate rtl scroll x; + if (getInnerView().getChildCount() > 0) { + int totalWidth = getInnerView().getChildAt(0).getWidth(); + int displayWidth = getInnerView().getMeasuredWidth(); + viewXInScroller = totalWidth - (component.getAbsoluteX() - getAbsoluteX()) - displayWidth; + } else { + viewXInScroller = component.getAbsoluteX() - getAbsoluteX(); + } + offsetFloat = - offsetFloat; + } else { + viewXInScroller = component.getAbsoluteX() - getAbsoluteX(); + } scrollBy(viewXInScroller - getScrollX() + (int) offsetFloat, viewYInScroller - getScrollY() + (int) offsetFloat, smooth); } diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/WXSlider.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/WXSlider.java index 196c839e11..8e9474069f 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/component/WXSlider.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/WXSlider.java @@ -145,9 +145,9 @@ public LayoutParams getChildLayoutParams(WXComponent child,View childView, int w if (lp instanceof ViewGroup.MarginLayoutParams) { //expect indicator . if (child instanceof WXIndicator) { - ((ViewGroup.MarginLayoutParams) lp).setMargins(left, top, right, bottom); + this.setMarginsSupportRTL((ViewGroup.MarginLayoutParams) lp, left, top, right, bottom); } else { - ((ViewGroup.MarginLayoutParams) lp).setMargins(0, 0, 0, 0); + this.setMarginsSupportRTL((ViewGroup.MarginLayoutParams) lp, 0, 0, 0, 0); } } return lp; @@ -193,7 +193,7 @@ public void addSubView(View view, int index) { @Override public void run() { initIndex = getInitIndex(); - mViewPager.setCurrentItem(initIndex); + mViewPager.setCurrentItem(getRealIndex(initIndex)); initIndex = -1; initRunnable = null; } @@ -203,7 +203,7 @@ public void run() { mViewPager.postDelayed(initRunnable, 50); } else { if (!keepIndex) { - mViewPager.setCurrentItem(0); + mViewPager.setCurrentItem(getRealIndex(0)); } } if (mIndicator != null) { @@ -212,6 +212,12 @@ public void run() { } } + @Override + public void setLayout(WXComponent component) { + mAdapter.setLayoutDirectionRTL(this.isNativeLayoutRTL()); + super.setLayout(component); + } + @Override public void remove(WXComponent child, boolean destroy) { if (child == null || child.getHostView() == null || mAdapter == null) { @@ -274,9 +280,22 @@ private int getInitIndex(){ if(select >= mAdapter.getRealCount()){ select = select%mAdapter.getRealCount(); } + return select; } + private int getRealIndex(int idx) { + int retIdx = idx; + + if (mAdapter.getRealCount() > 0) { + if(idx >= mAdapter.getRealCount()) retIdx = mAdapter.getRealCount() - 1; + if (isNativeLayoutRTL()) { + retIdx = mAdapter.getRealCount() - 1 - retIdx; + } + } + retIdx = retIdx + 0; + return retIdx; + } @Override protected boolean setProperty(String key, Object param) { @@ -383,6 +402,8 @@ public void setIndex(int index) { initIndex = index; return; } + + index = getRealIndex(index); mViewPager.setCurrentItem(index); if (mIndicator != null && mIndicator.getHostView() != null && mIndicator.getHostView().getRealCurrentItem() != index) { diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/WXVContainer.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/WXVContainer.java index c9880b5754..3d797ed1ac 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/component/WXVContainer.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/WXVContainer.java @@ -131,7 +131,7 @@ public void applyLayoutAndEvent(WXComponent component) { lp.width = width; lp.height = height; if(lp instanceof ViewGroup.MarginLayoutParams){ - ((ViewGroup.MarginLayoutParams) lp).setMargins(left,top,right,bottom); + this.setMarginsSupportRTL((ViewGroup.MarginLayoutParams) lp, left, top, right, bottom); } } return lp; @@ -588,8 +588,7 @@ View getBoxShadowHost(boolean isClear) { int bottom = (int) (padding.get(CSSShorthand.EDGE.BOTTOM) + border.get(CSSShorthand.EDGE.BOTTOM)); ViewGroup.MarginLayoutParams layoutParams = new ViewGroup.MarginLayoutParams(hostView.getLayoutParams()) ; - layoutParams.setMargins(-left, -top, -right, -bottom); - + this.setMarginsSupportRTL(layoutParams, -left, -top, -right, -bottom); mBoxShadowHost.setLayoutParams(layoutParams); hostView.addView(mBoxShadowHost); diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/list/BasicListComponent.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/list/BasicListComponent.java index e377ad9164..a9a2c67910 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/component/list/BasicListComponent.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/list/BasicListComponent.java @@ -35,6 +35,7 @@ import android.support.v7.widget.StaggeredGridLayoutManager; import android.text.TextUtils; import android.util.SparseArray; +import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -173,6 +174,42 @@ public BasicListComponent(WXSDKInstance instance, WXVContainer parent, BasicComp stickyHelper = new WXStickyHelper(this); } + @Override + public void setMarginsSupportRTL(ViewGroup.MarginLayoutParams lp, int left, int top, int right, int bottom) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { + lp.setMargins(left, top, right, bottom); + lp.setMarginStart(left); + lp.setMarginEnd(right); + } else { + if (lp instanceof FrameLayout.LayoutParams) { + FrameLayout.LayoutParams lp_frameLayout = (FrameLayout.LayoutParams) lp; + if (this.isNativeLayoutRTL()) { + lp_frameLayout.gravity = Gravity.RIGHT | Gravity.TOP; + lp.setMargins(right, top, left, bottom); + } else { + lp_frameLayout.gravity = Gravity.LEFT | Gravity.TOP; + lp.setMargins(left, top, right, bottom); + } + } else { + lp.setMargins(left, top, right, bottom); + } + } + } + + @Override + public void setLayout(WXComponent component) { + if (TextUtils.isEmpty(component.getComponentType()) + || TextUtils.isEmpty(component.getRef()) || component.getLayoutPosition() == null + || component.getLayoutSize() == null) { + return; + } + if (component.getHostView() != null) { + int layoutDirection = component.isNativeLayoutRTL() ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR; + ViewCompat.setLayoutDirection(component.getHostView(), layoutDirection); + } + super.setLayout(component); + } + @Override protected void onHostViewInitialized(T host) { super.onHostViewInitialized(host); @@ -248,7 +285,8 @@ public void destroy() { } else { params.width = width; params.height = height; - params.setMargins(left, 0, right, 0); + + this.setMarginsSupportRTL(params, left, 0, right, 0); } return params; } @@ -331,8 +369,6 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { } }); - - bounceRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/list/GapItemDecoration.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/list/GapItemDecoration.java index 821d7ce4ab..aebb6c3fb6 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/component/list/GapItemDecoration.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/list/GapItemDecoration.java @@ -64,10 +64,17 @@ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, Recycle if (params.getSpanIndex() >= spanOffsets.length) { return; } - float spanOffset = listComponent.getSpanOffsets()[params.getSpanIndex()]; + + int index = listComponent.isNativeLayoutRTL() ? spanOffsets.length - params.getSpanIndex() - 1 : params.getSpanIndex(); + float spanOffset = listComponent.getSpanOffsets()[index]; int spanOffsetPx = Math.round(WXViewUtils.getRealPxByWidth(spanOffset, listComponent.getViewPortWidth())); - outRect.left = spanOffsetPx; - outRect.right = -spanOffsetPx; + if (listComponent.isNativeLayoutRTL()) { + outRect.left = -spanOffsetPx; + outRect.right = spanOffsetPx; + } else { + outRect.left = spanOffsetPx; + outRect.right = -spanOffsetPx; + } } } } diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/list/template/WXRecyclerTemplateList.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/list/template/WXRecyclerTemplateList.java index 916d895367..8efcc2f512 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/component/list/template/WXRecyclerTemplateList.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/list/template/WXRecyclerTemplateList.java @@ -1283,7 +1283,8 @@ private void removeFooterOrHeader(WXComponent child) { } else { params.width = width; params.height = height; - params.setMargins(left, 0, right, 0); + + this.setMarginsSupportRTL(params, left, 0, right, 0); } return params; } diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/list/template/jni/NativeRenderLayoutDirection.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/list/template/jni/NativeRenderLayoutDirection.java new file mode 100644 index 0000000000..443dcf6db7 --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/list/template/jni/NativeRenderLayoutDirection.java @@ -0,0 +1,25 @@ +/** + * 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 com.taobao.weex.ui.component.list.template.jni; + +public class NativeRenderLayoutDirection { + public static final int inherit = 0; + public static final int ltr = 1; + public static final int rtl = 2; +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/list/template/jni/NativeRenderObjectUtils.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/list/template/jni/NativeRenderObjectUtils.java index c477413b6d..f665e8b18f 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/component/list/template/jni/NativeRenderObjectUtils.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/list/template/jni/NativeRenderObjectUtils.java @@ -51,6 +51,7 @@ * */ public static native int nativeLayoutRenderObject(long ptr, float width, float height); + public static native int nativeRenderObjectGetLayoutDirectionFromPathNode(long ptr); /** * get child length * */ diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/module/WXDomModule.java b/android/sdk/src/main/java/com/taobao/weex/ui/module/WXDomModule.java index 787ae53522..8f8c957380 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/module/WXDomModule.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/module/WXDomModule.java @@ -26,6 +26,7 @@ import com.taobao.weex.dom.binding.JSONUtils; import com.taobao.weex.ui.action.ActionAddRule; import com.taobao.weex.ui.action.ActionGetComponentRect; +import com.taobao.weex.ui.action.ActionGetLayoutDirection; import com.taobao.weex.ui.action.ActionInvokeMethod; import com.taobao.weex.ui.action.GraphicActionBatchBegin; import com.taobao.weex.ui.action.GraphicActionBatchEnd; @@ -47,6 +48,7 @@ public static final String SCROLL_TO_ELEMENT = "scrollToElement"; public static final String ADD_RULE = "addRule"; public static final String GET_COMPONENT_RECT = "getComponentRect"; + public static final String GET_COMPONENT_DIRECTION = "getLayoutDirection"; public static final String WXDOM = "dom"; public static final String INVOKE_METHOD = "invokeMethod"; @@ -59,7 +61,7 @@ * Methods expose to js. Every method which will be called in js should add to this array. */ public static final String[] METHODS = {SCROLL_TO_ELEMENT, ADD_RULE, GET_COMPONENT_RECT, - INVOKE_METHOD, BATCH_BEGIN, BATCH_END}; + INVOKE_METHOD, GET_COMPONENT_DIRECTION, BATCH_BEGIN, BATCH_END}; public WXDomModule(WXSDKInstance instance){ mWXSDKInstance = instance; @@ -82,6 +84,14 @@ public Object callDomMethod(String method, JSONArray args, long... parseNanos) { try { switch (method) { + case GET_COMPONENT_DIRECTION: { + if(args == null){ + return null; + } + new ActionGetLayoutDirection(mWXSDKInstance, args.getString(0), args.getString(1)) + .executeActionOnRender(); + break; + } case SCROLL_TO_ELEMENT:{ if (args == null) { return null; diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/view/WXCirclePageAdapter.java b/android/sdk/src/main/java/com/taobao/weex/ui/view/WXCirclePageAdapter.java index 281c4e2ba9..1b5b219d83 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/view/WXCirclePageAdapter.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/view/WXCirclePageAdapter.java @@ -26,6 +26,7 @@ import com.taobao.weex.utils.WXLogUtils; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class WXCirclePageAdapter extends PagerAdapter { @@ -37,12 +38,26 @@ private List<View> shadow = new ArrayList<>(); private boolean needLoop = true; + public boolean isRTL = false; + private List<View> originalViews = new ArrayList<>(); + public WXCirclePageAdapter(List<View> views, boolean needLoop) { super(); this.views = new ArrayList<>(views); + this.originalViews = new ArrayList<>(views); this.needLoop = needLoop; } + public void setLayoutDirectionRTL(boolean isRTL) { + if (isRTL == this.isRTL) return; + this.isRTL = isRTL; + this.views = new ArrayList<>(this.originalViews); + if (isRTL) { + Collections.reverse(this.views); + } + ensureShadow(); + } + public WXCirclePageAdapter() { this(true); } @@ -56,7 +71,13 @@ public void addPageView(View view) { if (WXEnvironment.isApkDebugable()) { WXLogUtils.d("onPageSelected >>>> addPageView"); } - views.add(view); + + originalViews.add(view); + if (this.isRTL) { + views.add(0, view); + } else { + views.add(view); + } ensureShadow(); } @@ -65,6 +86,7 @@ public void removePageView(View view) { WXLogUtils.d("onPageSelected >>>> removePageView"); } views.remove(view); + originalViews.remove(view); ensureShadow(); } @@ -77,6 +99,10 @@ public void replacePageView(View oldView, View newView) { views.remove(index); views.add(index, newView); ensureShadow(); + + index = originalViews.indexOf(oldView); + originalViews.remove(index); + originalViews.add(index, newView); } @Override diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/view/WXCircleViewPager.java b/android/sdk/src/main/java/com/taobao/weex/ui/view/WXCircleViewPager.java index 5c79e5ba73..6c19615def 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/view/WXCircleViewPager.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/view/WXCircleViewPager.java @@ -317,13 +317,24 @@ public void setScrollable(boolean scrollable) { } private void showNextItem() { - if (!needLoop && superGetCurrentItem() == getRealCount() - 1) { - return; - } - if (getRealCount() == 2 && superGetCurrentItem() == 1) { - superSetCurrentItem(0, true); + if (this.getCirclePageAdapter() != null && this.getCirclePageAdapter().isRTL) { + if (!needLoop && superGetCurrentItem() == 0) { + return; + } + if (getRealCount() == 2 && superGetCurrentItem() == 0) { + superSetCurrentItem(1, true); + } else { + superSetCurrentItem(superGetCurrentItem() - 1, true); + } } else { - superSetCurrentItem(superGetCurrentItem() + 1, true); + if (!needLoop && superGetCurrentItem() == getRealCount() - 1) { + return; + } + if (getRealCount() == 2 && superGetCurrentItem() == 1) { + superSetCurrentItem(0, true); + } else { + superSetCurrentItem(superGetCurrentItem() + 1, true); + } } } } diff --git a/android/sdk/src/main/java/com/taobao/weex/utils/StaticLayoutProxy.java b/android/sdk/src/main/java/com/taobao/weex/utils/StaticLayoutProxy.java index a3584ce164..ac1d660ac1 100644 --- a/android/sdk/src/main/java/com/taobao/weex/utils/StaticLayoutProxy.java +++ b/android/sdk/src/main/java/com/taobao/weex/utils/StaticLayoutProxy.java @@ -31,6 +31,7 @@ */ public class StaticLayoutProxy { + private static Constructor<StaticLayout> layoutConstructor; public static StaticLayout create(CharSequence source, TextPaint paint, int width, Layout.Alignment align, float spacingmult, float spacingadd, @@ -40,6 +41,8 @@ public static StaticLayout create(CharSequence source, TextPaint paint, StaticLayout rtlLayout = createInternal(source, paint, width, align, textDir, spacingmult, spacingadd, includepad); if (rtlLayout != null) { return rtlLayout; + } else { + return new StaticLayout(source, paint, width, align, spacingmult, spacingadd, includepad); } } return new StaticLayout(source, paint, width, align, spacingmult, spacingadd, includepad); @@ -53,16 +56,19 @@ private static StaticLayout createInternal(CharSequence source, TextPaint paint, return null; } else { try { - Class<StaticLayout> clazz = StaticLayout.class; - Constructor<StaticLayout> constructor = clazz.getConstructor(CharSequence.class, TextPaint.class, - int.class, Layout.Alignment.class, TextDirectionHeuristic.class, - float.class, float.class, - boolean.class); - - if (constructor != null) { - return constructor.newInstance(source, paint, width, + if (layoutConstructor == null) { + Class<StaticLayout> clazz = StaticLayout.class; + Constructor<StaticLayout> constructor = clazz.getConstructor(CharSequence.class, TextPaint.class, + int.class, Layout.Alignment.class, TextDirectionHeuristic.class, + float.class, float.class, + boolean.class); + layoutConstructor = constructor; + } + if (layoutConstructor != null) { + return layoutConstructor.newInstance(source, paint, width, align, textDir, spacingmult, spacingadd, includepad); } + } catch (Throwable e) { e.printStackTrace(); } diff --git a/android/sdk/src/main/res/values-ldltr/isrtl.xml b/android/sdk/src/main/res/values-ldltr/isrtl.xml new file mode 100644 index 0000000000..5008feadd9 --- /dev/null +++ b/android/sdk/src/main/res/values-ldltr/isrtl.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!-- + + 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. + +--> +<resources> + <bool name="weex_is_right_to_left">false</bool> +</resources> \ No newline at end of file diff --git a/android/sdk/src/main/res/values-ldrtl/istrtl.xml b/android/sdk/src/main/res/values-ldrtl/istrtl.xml new file mode 100644 index 0000000000..47d9b1a8dc --- /dev/null +++ b/android/sdk/src/main/res/values-ldrtl/istrtl.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!-- + + 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. + +--> +<resources> + <bool name="weex_is_right_to_left">true</bool> +</resources> \ No newline at end of file diff --git a/ios/sdk/WeexSDK/Sources/Bridge/WXBridgeContext.m b/ios/sdk/WeexSDK/Sources/Bridge/WXBridgeContext.m index dd186de1ea..6d42333e75 100644 --- a/ios/sdk/WeexSDK/Sources/Bridge/WXBridgeContext.m +++ b/ios/sdk/WeexSDK/Sources/Bridge/WXBridgeContext.m @@ -850,13 +850,17 @@ - (void)executeJsService:(NSString *)script withName:(NSString *)name { if(self.frameworkLoadFinished) { WXAssert(script, @"param script required!"); - NSDictionary* funcInfo = @{ - @"func":@"executeJsService", - @"arg":name?:@"unsetScriptName" - }; - self.jsBridge.javaScriptContext[@"wxExtFuncInfo"] = funcInfo; + if ([self.jsBridge respondsToSelector:@selector(javaScriptContext)]) { + NSDictionary* funcInfo = @{ + @"func":@"executeJsService", + @"arg":name?:@"unsetScriptName" + }; + self.jsBridge.javaScriptContext[@"wxExtFuncInfo"] = funcInfo; + } [self.jsBridge executeJavascript:script]; - self.jsBridge.javaScriptContext[@"wxExtFuncInfo"] = nil; + if ([self.jsBridge respondsToSelector:@selector(javaScriptContext)]) { + self.jsBridge.javaScriptContext[@"wxExtFuncInfo"] = nil; + } if ([self.jsBridge exception]) { NSString *exception = [[self.jsBridge exception] toString]; diff --git a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h index fc7658d826..3aef1dc13d 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h +++ b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h @@ -283,6 +283,9 @@ typedef id (^WXDataBindingBlock)(NSDictionary *data, BOOL *needUpdate); - (void)_buildViewHierarchyLazily; + +- (void)_adjustForRTL; + - (BOOL)_isAffineTypeAs:(NSString *)type; @end diff --git a/ios/sdk/WeexSDK/Sources/Component/WXCycleSliderComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXCycleSliderComponent.mm index 402e592c7a..b8a25fa634 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXCycleSliderComponent.mm +++ b/ios/sdk/WeexSDK/Sources/Component/WXCycleSliderComponent.mm @@ -458,6 +458,39 @@ - (void)layoutDidFinish _recycleSliderView.currentIndex = _index; } +- (void)_buildViewHierarchyLazily { + [super _buildViewHierarchyLazily]; +} + +- (void)adjustForRTL +{ + if (![WXUtility enableRTLLayoutDirection]) return; + + // this is scroll rtl solution. + // scroll layout not use direction, use self tranform + if (self.view && _flexCssNode && _flexCssNode->getLayoutDirectionFromPathNode() == WeexCore::kDirectionRTL + ) { + WXRecycleSliderView *slider = (WXRecycleSliderView *)self.view; + CATransform3D transform = CATransform3DScale(CATransform3DIdentity, -1, 1, 1); + slider.scrollView.layer.transform = transform ; + } else { + WXRecycleSliderView *slider = (WXRecycleSliderView *)self.view; + slider.scrollView.layer.transform = CATransform3DIdentity ; + } + +} + +- (void)_adjustForRTL { + if (![WXUtility enableRTLLayoutDirection]) return; + + [super _adjustForRTL]; + [self adjustForRTL]; +} + +- (BOOL)shouldTransformSubviewsWhenRTL { + return YES; +} + - (void)viewDidUnload { [_childrenView removeAllObjects]; diff --git a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.mm index a27820222b..50256abfb5 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.mm +++ b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.mm @@ -19,6 +19,7 @@ #import "WXScrollerComponent.h" #import "WXComponent_internal.h" +#import "WXSDKInstance_private.h" #import "WXComponent.h" #import "WXDefine.h" #import "WXConvert.h" @@ -415,6 +416,33 @@ - (void)removeStickyComponent:(WXComponent *)sticky } } +- (void)adjustForRTL +{ + if (![WXUtility enableRTLLayoutDirection]) return; + + // this is scroll rtl solution. + // scroll layout not use direction, use self tranform + if (self.view && _flexCssNode && _flexCssNode->getLayoutDirectionFromPathNode() == WeexCore::kDirectionRTL + ) { + if (_transform) { + self.view.layer.transform = CATransform3DConcat(self.view.layer.transform, CATransform3DScale(CATransform3DIdentity, -1, 1, 1)); + } else { + self.view.layer.transform = CATransform3DScale(CATransform3DIdentity, -1, 1, 1); + } + } else { + if (!_transform) { + self.view.layer.transform = CATransform3DIdentity; + } + } +} + +- (void)_adjustForRTL { + if (![WXUtility enableRTLLayoutDirection]) return; + + [super _adjustForRTL]; + [self adjustForRTL]; +} + - (void)adjustSticky { if (![self isViewLoaded]) { @@ -1044,7 +1072,7 @@ - (void)_layoutPlatform float left = _flexCssNode->getLayoutPositionLeft(); float width = _flexCssNode->getLayoutWidth(); float height = _flexCssNode->getLayoutHeight(); - + if (_scrollDirection == WXScrollDirectionVertical) { _flexCssNode->setFlexDirection(WeexCore::kFlexDirectionColumn, NO); _flexCssNode->setStyleWidth(_flexCssNode->getLayoutWidth(), NO); @@ -1054,7 +1082,14 @@ - (void)_layoutPlatform _flexCssNode->setStyleHeight(_flexCssNode->getLayoutHeight()); _flexCssNode->setStyleWidth(FlexUndefined, NO); } + _flexCssNode->markAllDirty(); + + // this is scroll rtl solution. + // scroll layout not use direction, use self tranform + // but we need inherit direction in CSS, so we set children layout diretion manually + _flexCssNode->determineChildLayoutDirection(_flexCssNode->getLayoutDirectionFromPathNode()); + std::pair<float, float> renderPageSize; renderPageSize.first = self.weexInstance.frame.size.width; renderPageSize.second = self.weexInstance.frame.size.height; diff --git a/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm index e8683aac9c..f4c21a9527 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm +++ b/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm @@ -139,7 +139,6 @@ @implementation WXTextComponent WXTextStyle _fontStyle; NSUInteger _lines; NSTextAlignment _textAlign; - NSString *_direction; WXTextDecoration _textDecoration; NSString *_textOverflow; CGFloat _lineHeight; @@ -273,7 +272,6 @@ - (void)fillCSSStyles:(NSDictionary *)styles WX_STYLE_FILL_TEXT_PIXEL(lineHeight, lineHeight, YES) WX_STYLE_FILL_TEXT_PIXEL(letterSpacing, letterSpacing, YES) WX_STYLE_FILL_TEXT(wordWrap, wordWrap, NSString, YES); - WX_STYLE_FILL_TEXT(direction, direction, NSString, YES) if (_fontFamily && !_observerIconfont) { // notification received when custom icon font file download finish [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(repaintText:) name:WX_ICONFONT_DOWNLOAD_NOTIFICATION object:nil]; @@ -515,7 +513,7 @@ - (NSMutableAttributedString *)buildCTAttributeString NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; // handle text direction style, default ltr - BOOL isRtl = [_direction isEqualToString:@"rtl"]; + BOOL isRtl = [self isDirectionRTL]; if (isRtl) { if (0 == _textAlign) { //force text right-align if don't specified any align. @@ -598,7 +596,7 @@ - (NSAttributedString *)buildAttributeString NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; // handle text direction style, default ltr - BOOL isRtl = [_direction isEqualToString:@"rtl"]; + BOOL isRtl = [self isDirectionRTL]; if (isRtl) { if (0 == _textAlign) { //force text right-align if don't specified any align. diff --git a/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.h b/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.h index a33884769c..518a163fae 100644 --- a/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.h +++ b/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.h @@ -43,6 +43,7 @@ extern "C" { CGRect _calculatedFrame; CGPoint _absolutePosition; WXPositionType _positionType; + BOOL _isLastLayoutDirectionRTL; } /** @@ -69,4 +70,13 @@ extern "C" { */ - (void)removeSubcomponentCssNode:(WXComponent *)subcomponent; +#pragma mark - RTL + +@property (nonatomic, assign, readonly) BOOL isDirectionRTL; + +// Now we scrollView RTL solution is tranform +// so scrollView need tranform subviews when RTL by default +// if your component view is not scrollView but also implement RTL layout by tranform,you need return YES +- (BOOL)shouldTransformSubviewsWhenRTL; + @end diff --git a/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.mm b/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.mm index 18cb24dbb1..14246e7a37 100644 --- a/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.mm +++ b/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.mm @@ -153,11 +153,26 @@ - (void)_frameDidCalculated:(BOOL)isChanged [strongSelf->_transform applyTransformForView:strongSelf.view]; } + [self _adjustForRTL]; + if (strongSelf->_backgroundImage) { [strongSelf setGradientLayer]; } [strongSelf setNeedsDisplay]; }]; + } else { + // if frame is not change, we still need check was layoutDirection changed + if ([self isDirectionRTL] != _isLastLayoutDirectionRTL) { + _isLastLayoutDirectionRTL = [self isDirectionRTL]; + __weak typeof(self) weakSelf = self; + [self.weexInstance.componentManager _addUITask:^{ + __strong typeof(weakSelf) strongSelf = weakSelf; + if (strongSelf->_transform) { + [strongSelf->_transform applyTransformForView:strongSelf.view]; + } + [strongSelf _adjustForRTL]; + }]; + } } } @@ -168,6 +183,7 @@ - (void)_layoutDidFinish if (_positionType == WXPositionTypeSticky) { [self.ancestorScroller adjustSticky]; } + [self layoutDidFinish]; } @@ -183,9 +199,15 @@ - (void)_fillCSSNode:(NSDictionary *)styles isUpdate:(BOOL)isUpdate if (_flexCssNode == nullptr) { return; } - + BOOL needLayout = NO; + // CSS direction for RTL Layout + if (styles[@"direction"]) { + _flexCssNode->setDirection([self fxDirection:styles[@"direction"]], isUpdate); + needLayout = YES; + } + // flex if (styles[@"flex"]) { _flexCssNode->set_flex([WXConvert CGFloat:styles[@"flex"]]); @@ -453,7 +475,9 @@ - (void)_resetCSSNode:(NSArray *)styles if (styles.count<=0) { return; } - + + WX_FLEX_STYLE_RESET_CSS_NODE(direction, @(WeexCore::kDirectionInherit)) + WX_FLEX_STYLE_RESET_CSS_NODE(flex, @0.0) WX_FLEX_STYLE_RESET_CSS_NODE(flexDirection, @(WeexCore::kFlexDirectionColumn)) WX_FLEX_STYLE_RESET_CSS_NODE(alignItems, @(WeexCore::kAlignItemsStretch)) @@ -563,6 +587,18 @@ - (void)_resetCSSNode:(NSArray *)styles return WeexCore::kRelative; } +- (WeexCore::WXCoreDirection)fxDirection:(id)value +{ + if([value isKindOfClass:[NSString class]]){ + if ([value isEqualToString:@"rtl"]) { + return WeexCore::kDirectionRTL; + } else if ([value isEqualToString:@"ltr"]) { + return WeexCore::kDirectionLTR; + } + } + return WeexCore::kDirectionInherit; +} + - (WeexCore::WXCoreFlexDirection)fxFlexDirection:(id)value { if([value isKindOfClass:[NSString class]]){ @@ -677,4 +713,39 @@ - (void)removeSubcomponentCssNode:(WXComponent *)subcomponent } } +#pragma mark - RTL + +- (BOOL)isDirectionRTL { + if (![WXUtility enableRTLLayoutDirection]) return NO; + + WeexCore::WXCoreDirection direction = _flexCssNode == nullptr ? WeexCore::WEEXCORE_CSS_DEFAULT_DIRECTION : _flexCssNode->getLayoutDirectionFromPathNode(); + if (direction != WeexCore::kDirectionInherit) return direction == WeexCore::kDirectionRTL; + return NO; +} + +- (void)_adjustForRTL { + if (![WXUtility enableRTLLayoutDirection]) return; + + if (self->_positionType == WXPositionTypeFixed) return; + + if (self.supercomponent && self.supercomponent->_flexCssNode && self.supercomponent->_flexCssNode->getLayoutDirectionFromPathNode() == WeexCore::kDirectionRTL && [self.supercomponent shouldTransformSubviewsWhenRTL]) { + if (_transform) { + self.view.layer.transform = CATransform3DConcat(self.view.layer.transform, CATransform3DScale(CATransform3DIdentity, -1, 1, 1)); + } else { + self.view.layer.transform = CATransform3DScale(CATransform3DIdentity, -1, 1, 1); + } + } else { + if (!_transform) { + self.view.layer.transform = CATransform3DIdentity; + } + } +} + +// Now we scrollView RTL solution is tranform +// so scrollView need tranform subviews when RTL by default +// if your component view is not scrollView but also implement RTL layout by tranform,you need return YES +- (BOOL)shouldTransformSubviewsWhenRTL { + return [self.view isKindOfClass:[UIScrollView class]]; +} + @end diff --git a/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm b/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm index 43fda8b476..f69f506a4e 100644 --- a/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm +++ b/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm @@ -383,11 +383,13 @@ - (UIView *)view if (_backgroundImage) { [self setGradientLayer]; } - + if (_transform) { [_transform applyTransformForView:_view]; } + [self _adjustForRTL]; + if (_boxShadow) { [self configBoxShadow:_boxShadow]; } @@ -801,6 +803,7 @@ - (void)setNativeTransform:(CGAffineTransform)transform self.transform = [[WXTransform alloc] initWithNativeTransform:CATransform3DMakeAffineTransform(transform) instance:self.weexInstance]; if (!CGRectEqualToRect(self.calculatedFrame, CGRectZero)) { [_transform applyTransformForView:_view]; + [self _adjustForRTL]; [_layer setNeedsDisplay]; } } diff --git a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m index e0cc3b8368..4a04c1443b 100644 --- a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m +++ b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m @@ -447,6 +447,9 @@ - (BOOL)_handleConfigCenter BOOL useJSCApiForCreateInstance = [[configCenter configForKey:@"iOS_weex_ext_config.useJSCApiForCreateInstance" defaultValue:@(YES) isDefault:NULL] boolValue]; [WXUtility setUseJSCApiForCreateInstance:useJSCApiForCreateInstance]; + + BOOL enableRTLLayoutDirection = [[configCenter configForKey:@"iOS_weex_ext_config.enableRTLLayoutDirection" defaultValue:@(YES) isDefault:NULL] boolValue]; + [WXUtility setEnableRTLLayoutDirection:enableRTLLayoutDirection]; BOOL shoudMultiContext = NO; shoudMultiContext = [[configCenter configForKey:@"iOS_weex_ext_config.createInstanceUsingMutliContext" defaultValue:@(YES) isDefault:NULL] boolValue]; diff --git a/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m b/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m index 75b09d8d01..3b52110ab9 100644 --- a/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m +++ b/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m @@ -93,6 +93,7 @@ -(void)applyTransform if ([_animationInfo.propertyName hasPrefix:@"transform"]) { WXTransform *transform = _animationInfo.target->_transform; [transform applyTransformForView:_animationInfo.target.view]; + [_animationInfo.target _adjustForRTL]; } else if ([_animationInfo.propertyName isEqualToString:@"backgroundColor"]) { _animationInfo.target.view.layer.backgroundColor = (__bridge CGColorRef _Nullable)(_animationInfo.toValue); } else if ([_animationInfo.propertyName isEqualToString:@"opacity"]) { diff --git a/ios/sdk/WeexSDK/Sources/Module/WXDomModule.m b/ios/sdk/WeexSDK/Sources/Module/WXDomModule.m index 312d6f879e..a3de95b683 100644 --- a/ios/sdk/WeexSDK/Sources/Module/WXDomModule.m +++ b/ios/sdk/WeexSDK/Sources/Module/WXDomModule.m @@ -18,6 +18,7 @@ */ #import "WXDomModule.h" +#import "WXComponent+Layout.h" #import "WXDefine.h" #import "WXSDKManager.h" #import "WXComponentManager.h" @@ -54,6 +55,7 @@ @implementation WXDomModule WX_EXPORT_METHOD(@selector(updateAttrs:attrs:)) WX_EXPORT_METHOD(@selector(addRule:rule:)) WX_EXPORT_METHOD(@selector(getComponentRect:callback:)) +WX_EXPORT_METHOD(@selector(getLayoutDirection:callback:)) WX_EXPORT_METHOD(@selector(updateComponentData:componentData:callback:)) WX_EXPORT_METHOD(@selector(beginBatchMark)) WX_EXPORT_METHOD(@selector(endBatchMark)) @@ -261,6 +263,30 @@ - (void)getComponentRect:(NSString*)ref callback:(WXModuleKeepAliveCallback)call }]; } +- (void)getLayoutDirection:(NSString*)ref callback:(WXModuleKeepAliveCallback)callback { + [self performBlockOnComponentManager:^(WXComponentManager * manager) { + if ([ref isEqualToString:@"viewport"]) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *direction = [WXUtility getEnvLayoutDirection] == WXLayoutDirectionRTL ? @"rtl" : @"ltr"; + if (callback) { + callback(direction, false); + } + }); + } else { + WXComponent *component = [manager componentForRef:ref]; + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *direction = @"unknow"; + if (component) { + direction = [component isDirectionRTL] ? @"rtl" : @"ltr"; + } + if (callback) { + callback(direction, false); + } + }); + } + }]; +} + - (void)updateComponentData:(NSString*)componentDataId componentData:(NSDictionary*)componentData callback:(NSString*)callbackId { NSString *recycleListComponentRef = [[componentDataId componentsSeparatedByString:@"@"] objectAtIndex:0]; diff --git a/ios/sdk/WeexSDK/Sources/Protocol/WXScrollerProtocol.h b/ios/sdk/WeexSDK/Sources/Protocol/WXScrollerProtocol.h index 6cbb245e31..9b72ba3f2f 100644 --- a/ios/sdk/WeexSDK/Sources/Protocol/WXScrollerProtocol.h +++ b/ios/sdk/WeexSDK/Sources/Protocol/WXScrollerProtocol.h @@ -73,7 +73,13 @@ - (WXScrollDirection)scrollDirection; @optional + - (NSString*)refreshType; - (BOOL)requestGestureShouldStopPropagation:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch; +/** + * @abstract adjust for RTL + */ +- (void)adjustForRTL; + @end diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXType.h b/ios/sdk/WeexSDK/Sources/Utility/WXType.h index a33b9cc307..2ef5c84ac3 100644 --- a/ios/sdk/WeexSDK/Sources/Utility/WXType.h +++ b/ios/sdk/WeexSDK/Sources/Utility/WXType.h @@ -20,6 +20,12 @@ #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> +typedef NS_ENUM(NSUInteger, WXLayoutDirection) { + WXLayoutDirectionLTR, + WXLayoutDirectionRTL, + WXLayoutDirectionAuto, +}; + typedef NS_ENUM(NSUInteger, WXComponentType) { WXComponentTypeCommon = 0, WXComponentTypeVirtual diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h index 601727a6eb..730c56080b 100644 --- a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h +++ b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h @@ -131,6 +131,8 @@ _Nonnull SEL WXSwizzledSelectorForSelector(_Nonnull SEL selector); + (NSDictionary *_Nonnull)getDebugEnvironment; ++ (WXLayoutDirection)getEnvLayoutDirection; + /** * @abstract UserAgent Generation * @@ -489,6 +491,10 @@ BOOL WXFloatGreaterThanWithPrecision(CGFloat a,CGFloat b,double precision); + (BOOL)useJSCApiForCreateInstance; ++ (void)setEnableRTLLayoutDirection:(BOOL)value; + ++ (BOOL)enableRTLLayoutDirection; + + (long) getUnixFixTimeMillis; + (NSArray<NSString *> *_Nullable)extractPropertyNamesOfJSValueObject:(JSValue *_Nullable)jsvalue; diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m index 121a73c9c2..b070a87baf 100644 --- a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m +++ b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m @@ -44,6 +44,7 @@ static BOOL unregisterFontWhenCollision = NO; static BOOL useJSCApiForCreateInstance = YES; +static BOOL enableRTLLayoutDirection = YES; void WXPerformBlockOnMainThread(void (^ _Nonnull block)(void)) { @@ -173,6 +174,18 @@ + (void)_performBlock:(void (^)(void))block block(); } ++ (WXLayoutDirection)getEnvLayoutDirection { + // We not use the below technique, because your app maybe not support the first preferredLanguages + // _sysLayoutDirection = [NSLocale characterDirectionForLanguage:[[NSLocale preferredLanguages] objectAtIndex:0]] == NSLocaleLanguageDirectionRightToLeft ? WXLayoutDirectionRTL : WXLayoutDirectionLTR; + if (@available(iOS 9.0, *)) { + // The view is shown in right-to-left mode right now. + return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:UISemanticContentAttributeUnspecified] == UIUserInterfaceLayoutDirectionRightToLeft ? WXLayoutDirectionRTL : WXLayoutDirectionLTR; + } else { + // Use the previous technique + return [[UIApplication sharedApplication] userInterfaceLayoutDirection] == UIUserInterfaceLayoutDirectionRightToLeft ? WXLayoutDirectionRTL : WXLayoutDirectionLTR; + } +} + + (NSDictionary *)getEnvironment { NSString *platform = @"iOS"; @@ -197,7 +210,8 @@ + (NSDictionary *)getEnvironment @"deviceWidth":@(deviceWidth * scale), @"deviceHeight":@(deviceHeight * scale), @"scale":@(scale), - @"logLevel":[WXLog logLevelString] ?: @"error" + @"logLevel":[WXLog logLevelString] ?: @"error", + @"layoutDirection": [self getEnvLayoutDirection] == WXLayoutDirectionRTL ? @"rtl" : @"ltr" }]; if ([[[UIDevice currentDevice] systemVersion] integerValue] >= 11) { @@ -736,6 +750,17 @@ + (CGFloat)defaultPixelScaleFactor return defaultScaleFactor; } +#pragma mark - RTL + ++ (void)setEnableRTLLayoutDirection:(BOOL)value +{ + enableRTLLayoutDirection = value; +} + ++ (BOOL)enableRTLLayoutDirection +{ + return enableRTLLayoutDirection; +} #pragma mark - get deviceID + (NSString *)getDeviceID { diff --git a/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm b/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm index ad178903bc..98cf737f02 100644 --- a/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm +++ b/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm @@ -263,6 +263,7 @@ - (void)_updateViewStyles:(NSDictionary *)styles WXTransform* transform = [[WXTransform alloc] initWithCSSValue:[WXConvert NSString:styles[@"transform"]] origin:[WXConvert NSString:transformOrigin] instance:self.weexInstance]; if (!CGRectEqualToRect(self.calculatedFrame, CGRectZero)) { [transform applyTransformForView:_view]; + [self _adjustForRTL]; [_layer setNeedsDisplay]; } self.transform = transform; @@ -270,9 +271,14 @@ - (void)_updateViewStyles:(NSDictionary *)styles [_transform setTransformOrigin:[WXConvert NSString:styles[@"transformOrigin"]]]; if (!CGRectEqualToRect(self.calculatedFrame, CGRectZero)) { [_transform applyTransformForView:_view]; + [self _adjustForRTL]; [_layer setNeedsDisplay]; } } + // for RTL + if (styles[@"direction"]) { + [self _adjustForRTL]; + } } - (void)resetBorder:(NSArray *)styles diff --git a/weex_core/Source/android/jniprebuild/jniheader/NativeRenderObjectUtils_jni.h b/weex_core/Source/android/jniprebuild/jniheader/NativeRenderObjectUtils_jni.h index 55eefa2760..78c51c7020 100644 --- a/weex_core/Source/android/jniprebuild/jniheader/NativeRenderObjectUtils_jni.h +++ b/weex_core/Source/android/jniprebuild/jniheader/NativeRenderObjectUtils_jni.h @@ -50,6 +50,9 @@ static void AddChildRenderObject(JNIEnv* env, jclass jcaller, jlong parent, jlong child); +static jint RenderObjectGetLayoutDirectionFromPathNode(JNIEnv* env, jclass jcaller, + jlong ptr); + static jboolean RenderObjectHasNewLayout(JNIEnv* env, jclass jcaller, jlong ptr); @@ -147,6 +150,11 @@ static const JNINativeMethod kMethodsNativeRenderObjectUtils[] = { "J" ")" "V", reinterpret_cast<void*>(AddChildRenderObject) }, + { "nativeRenderObjectGetLayoutDirectionFromPathNode", +"(" +"J" +")" +"I", reinterpret_cast<void*>(RenderObjectGetLayoutDirectionFromPathNode) }, { "nativeRenderObjectHasNewLayout", "(" "J" diff --git a/weex_core/Source/android/utils/params_utils.cpp b/weex_core/Source/android/utils/params_utils.cpp index be663eedd0..47651efff2 100644 --- a/weex_core/Source/android/utils/params_utils.cpp +++ b/weex_core/Source/android/utils/params_utils.cpp @@ -293,6 +293,22 @@ std::vector<INIT_FRAMEWORK_PARAMS*> initFromParam( ADDSTRING(appVersion); env->DeleteLocalRef(appVersion); + jmethodID m_layoutDirection = + env->GetMethodID(c_params, "getLayoutDirection", "()Ljava/lang/String;"); + if (m_layoutDirection == nullptr) { + ADDSTRING(nullptr); + ReportNativeInitStatus("-1012", "get m_layoutDirection failed"); + return initFrameworkParams; + } + jobject layoutDirection = env->CallObjectMethod(params, m_layoutDirection); + if (layoutDirection == nullptr) { + ADDSTRING(nullptr); + ReportNativeInitStatus("-1012", "get layoutDirection failed"); + return initFrameworkParams; + } + ADDSTRING(layoutDirection); + env->DeleteLocalRef(layoutDirection); + jmethodID m_weexVersion = env->GetMethodID(c_params, "getWeexVersion", "()Ljava/lang/String;"); if (m_weexVersion == nullptr) { diff --git a/weex_core/Source/android/wrap/native_render_object_utils_impl_android.cpp b/weex_core/Source/android/wrap/native_render_object_utils_impl_android.cpp index 5565075a01..8b6d9f1f0c 100644 --- a/weex_core/Source/android/wrap/native_render_object_utils_impl_android.cpp +++ b/weex_core/Source/android/wrap/native_render_object_utils_impl_android.cpp @@ -128,8 +128,11 @@ static jint LayoutRenderObject(JNIEnv* env, jclass jcaller, return (jint)render->getLayoutHeight(); } - - +static jint RenderObjectGetLayoutDirectionFromPathNode(JNIEnv* env, jclass jcaller, + jlong ptr){ + RenderObject* renderObject = convert_long_to_render_object(ptr); + return renderObject->getLayoutDirectionFromPathNode(); +} static jboolean RenderObjectHasNewLayout(JNIEnv* env, jclass jcaller, jlong ptr){ diff --git a/weex_core/Source/core/css/constants_name.h b/weex_core/Source/core/css/constants_name.h index 48b8a5910d..fe282ccab2 100644 --- a/weex_core/Source/core/css/constants_name.h +++ b/weex_core/Source/core/css/constants_name.h @@ -23,6 +23,7 @@ #include <string> namespace WeexCore { + constexpr char DIRECTION[] = "direction"; constexpr char FLEX[] = "flex"; constexpr char HORIZONTAL[] = "horizontal"; diff --git a/weex_core/Source/core/css/constants_value.h b/weex_core/Source/core/css/constants_value.h index d51ebb52ef..b2a18621ce 100644 --- a/weex_core/Source/core/css/constants_value.h +++ b/weex_core/Source/core/css/constants_value.h @@ -23,6 +23,10 @@ #include <string> namespace WeexCore { + // direction + constexpr char RTL[] = "rtl"; + constexpr char LTR[] = "ltr"; + constexpr char INHERIT[] = "inherit"; // flex-direction constexpr char ROW[] = "row"; diff --git a/weex_core/Source/core/css/css_value_getter.cpp b/weex_core/Source/core/css/css_value_getter.cpp index f576245e32..a60529f159 100644 --- a/weex_core/Source/core/css/css_value_getter.cpp +++ b/weex_core/Source/core/css/css_value_getter.cpp @@ -24,6 +24,17 @@ #include "core/layout/style.h" namespace WeexCore { + const WXCoreDirection GetWXCoreDirection(const std::string &value) { + const char *c_value = value.c_str(); + if(strcmp(c_value, INHERIT) == 0) { + return WeexCore::kDirectionInherit; + } else if (strcmp(c_value, LTR) == 0) { + return WeexCore::kDirectionLTR; + } else if (strcmp(c_value, RTL) == 0) { + return WeexCore::kDirectionRTL; + } + return WeexCore::kDirectionLTR; + } const WXCoreFlexDirection GetWXCoreFlexDirection(const std::string &value) { const char *c_value = value.c_str(); diff --git a/weex_core/Source/core/css/css_value_getter.h b/weex_core/Source/core/css/css_value_getter.h index f47abc18d0..ec630ca9b9 100644 --- a/weex_core/Source/core/css/css_value_getter.h +++ b/weex_core/Source/core/css/css_value_getter.h @@ -23,6 +23,7 @@ #include <string> namespace WeexCore { + const WXCoreDirection GetWXCoreDirection(const std::string &value); const WXCoreFlexDirection GetWXCoreFlexDirection(const std::string &value); diff --git a/weex_core/Source/core/layout/flex_enum.h b/weex_core/Source/core/layout/flex_enum.h index 9eb3676354..6f63e99c39 100644 --- a/weex_core/Source/core/layout/flex_enum.h +++ b/weex_core/Source/core/layout/flex_enum.h @@ -16,13 +16,23 @@ * specific language governing permissions and limitations * under the License. */ + #ifdef __cplusplus #ifndef WEEXCORE_FLEXLAYOUT_WXCOREFLEXENUM_H #define WEEXCORE_FLEXLAYOUT_WXCOREFLEXENUM_H -namespace WeexCore { +#define WEEXCORE_CSS_DEFAULT_DIRECTION kDirectionLTR +namespace WeexCore { + /** + * MainAxis direction + */ + enum WXCoreDirection { + kDirectionInherit, + kDirectionLTR, + kDirectionRTL + }; /** * MainAxis direction */ diff --git a/weex_core/Source/core/layout/layout.cpp b/weex_core/Source/core/layout/layout.cpp index 6b7a2900b4..8ffdf48b28 100644 --- a/weex_core/Source/core/layout/layout.cpp +++ b/weex_core/Source/core/layout/layout.cpp @@ -645,21 +645,39 @@ namespace WeexCore { void WXCoreLayoutNode::onLayout(const float left, const float top, const float right, const float bottom, WXCoreLayoutNode *const absoulteItem, WXCoreFlexLine *const flexLine) { - switch (mCssStyle->mFlexDirection) { - case kFlexDirectionRow: - layoutHorizontal(false, left, top, right, bottom, absoulteItem, flexLine); - break; - case kFlexDirectionRowReverse: - layoutHorizontal(true, left, top, right, bottom, absoulteItem, flexLine); - break; - case kFlexDirectionColumnReverse: - layoutVertical(mCssStyle->mFlexWrap == kWrapReverse, true, left, top, right, bottom, absoulteItem, flexLine); - break; - case kFlexDirectionColumn: - default: - layoutVertical(mCssStyle->mFlexWrap == kWrapReverse, false, left, top, right, bottom, absoulteItem, flexLine); - break; - } + // determin direction + if (mLayoutResult->mLayoutDirection == kDirectionInherit) { + if(mCssStyle->mDirection == kDirectionInherit) { + // default direction in css is inherit, inherit direction from parent node + mLayoutResult->mLayoutDirection = NULL == mParent ? WEEXCORE_CSS_DEFAULT_DIRECTION : mParent->getLayoutDirection(); + } else { + // specific direction in current Node's style + mLayoutResult->mLayoutDirection = mCssStyle->mDirection; + } + } + + bool verticalRTL = false; + if (mCssStyle->mFlexWrap != kNoWrap && mLayoutResult->mLayoutDirection == kDirectionRTL) { + verticalRTL = mCssStyle->mFlexWrap != kWrapReverse; + } else { + verticalRTL = mCssStyle->mFlexWrap == kWrapReverse; + } + + switch (mCssStyle->mFlexDirection) { + case kFlexDirectionRow: + layoutHorizontal(mLayoutResult->mLayoutDirection == kDirectionRTL, left, top, right, bottom, absoulteItem, flexLine); + break; + case kFlexDirectionRowReverse: + layoutHorizontal(mLayoutResult->mLayoutDirection != kDirectionRTL, left, top, right, bottom, absoulteItem, flexLine); + break; + case kFlexDirectionColumnReverse: + layoutVertical(verticalRTL, true, left, top, right, bottom, absoulteItem, flexLine); + break; + case kFlexDirectionColumn: + default: + layoutVertical(verticalRTL, false, left, top, right, bottom, absoulteItem, flexLine); + break; + } } /** @@ -1083,6 +1101,34 @@ namespace WeexCore { break; } } + void WXCoreLayoutNode::determineChildLayoutDirection(const WXCoreDirection direction) { + for (Index i = 0; i < getChildCount(kBFC); ++i) { + WXCoreLayoutNode *child = getChildAt(kBFC, i); + // determin direction + if (child->mLayoutResult->mLayoutDirection == kDirectionInherit) { + if(child->mCssStyle->mDirection == kDirectionInherit) { + // default direction in css is inherit, inherit direction from parent node + child->mLayoutResult->mLayoutDirection = direction; + } else { + // specific direction in current Node's style + child->mLayoutResult->mLayoutDirection = child->mCssStyle->mDirection; + } + } + } + } + + WXCoreDirection WXCoreLayoutNode::getLayoutDirectionFromPathNode() { + WXCoreLayoutNode *node = this; + if (node->getLayoutDirection() != kDirectionInherit) return node->getLayoutDirection(); + if (node->getDirection() != kDirectionInherit) { + node->mLayoutResult->mLayoutDirection = node->getDirection(); + return node->getLayoutDirection(); + } else if (nullptr != node->mParent) { + node->mLayoutResult->mLayoutDirection = node->mParent->getLayoutDirectionFromPathNode(); + return node->getLayoutDirection(); + } + return WEEXCORE_CSS_DEFAULT_DIRECTION; + } } diff --git a/weex_core/Source/core/layout/layout.h b/weex_core/Source/core/layout/layout.h index 9434932700..bd748e46e1 100644 --- a/weex_core/Source/core/layout/layout.h +++ b/weex_core/Source/core/layout/layout.h @@ -72,9 +72,10 @@ namespace WeexCore { }; /** - * layout-result:layout-height、layout-width、position(left、right、top、bottom) + * layout-result:layout-height、layout-width、position(left、right、top、bottom)、direction */ struct WXCorelayoutResult { + WXCoreDirection mLayoutDirection; WXCoreSize mLayoutSize; WXCorePosition mLayoutPosition; @@ -83,8 +84,9 @@ namespace WeexCore { } inline void reset() { - mLayoutSize.reset(); - mLayoutPosition.reset(); + mLayoutSize.reset(); + mLayoutPosition.reset(); + mLayoutDirection = kDirectionInherit; } }; @@ -151,7 +153,6 @@ namespace WeexCore { mLayoutResult = new WXCorelayoutResult(); } - virtual ~WXCoreLayoutNode() { mIsDestroy = true; mHasNewLayout = true; @@ -257,20 +258,22 @@ namespace WeexCore { inline void setContext(void * const context) { this->context = context; } - + inline void copyStyle(WXCoreLayoutNode *srcNode) { - if (memcmp(mCssStyle, srcNode->mCssStyle, sizeof(WXCoreCSSStyle)) != 0) { + if (srcNode != nullptr && memcmp(mCssStyle, srcNode->mCssStyle, sizeof(WXCoreCSSStyle)) != 0) { memcpy(mCssStyle, srcNode->mCssStyle, sizeof(WXCoreCSSStyle)); markDirty(); } } - + void copyFrom(WXCoreLayoutNode* srcNode){ - memcpy(mCssStyle, srcNode->mCssStyle, sizeof(WXCoreCSSStyle)); + if (srcNode == nullptr) return; + + memcpy(mCssStyle, srcNode->mCssStyle, sizeof(WXCoreCSSStyle)); } inline void copyMeasureFunc(WXCoreLayoutNode *srcNode) { - if (memcmp(&measureFunc, &srcNode->measureFunc, sizeof(WXCoreMeasureFunc)) != 0) { + if (srcNode != nullptr && memcmp(&measureFunc, &srcNode->measureFunc, sizeof(WXCoreMeasureFunc)) != 0) { memcpy(&measureFunc, &srcNode->measureFunc, sizeof(WXCoreMeasureFunc)); markDirty(); } @@ -607,8 +610,6 @@ namespace WeexCore { void positionAbsoluteFlexItem(float &left, float &top, float &right, float &bottom); - void onLayout(float left, float top, float right, float bottom, WXCoreLayoutNode* = nullptr, WXCoreFlexLine *const flexLine = nullptr); - void layoutHorizontal(bool isRtl, float left, float top, float right, float bottom, WXCoreLayoutNode*, WXCoreFlexLine *const flexLine); @@ -668,7 +669,7 @@ namespace WeexCore { public: - + virtual void onLayout(float left, float top, float right, float bottom, WXCoreLayoutNode* = nullptr, WXCoreFlexLine *const flexLine = nullptr); /** ================================ tree =================================== **/ inline Index getChildCount(FormattingContext formattingContext) const { @@ -972,7 +973,33 @@ namespace WeexCore { return mCssStyle->mMaxHeight; } + inline void setDirection(const WXCoreDirection direction, const bool updating) { + if (nullptr == mCssStyle) return; + + if (mCssStyle->mDirection != direction) { + mCssStyle->mDirection = direction; + markDirty(); + if (updating) { + for (auto it = ChildListIterBegin(); it != ChildListIterEnd(); it++) { + (*it)->markInheritableDirty(); + } + } + } + } + inline WXCoreDirection getDirection() const { + if (mCssStyle == nullptr) { + return WEEXCORE_CSS_DEFAULT_DIRECTION; + } + return mCssStyle->mDirection; + } + + /** ================================ CSS direction For RTL =================================== **/ + + void determineChildLayoutDirection(const WXCoreDirection direction); + + WXCoreDirection getLayoutDirectionFromPathNode(); + /** ================================ flex-style =================================== **/ inline void setFlexDirection(const WXCoreFlexDirection flexDirection, const bool updating) { @@ -1070,7 +1097,18 @@ namespace WeexCore { inline float getLayoutPositionRight() const { return mLayoutResult->mLayoutPosition.getPosition(kPositionEdgeRight); } + + virtual inline WXCoreDirection getLayoutDirection() const { + if (nullptr == mLayoutResult) { + return WEEXCORE_CSS_DEFAULT_DIRECTION; + } + return mLayoutResult->mLayoutDirection; + } + inline void setLayoutDirection(WXCoreDirection direction) { + if (nullptr == mLayoutResult) return; + mLayoutResult->mLayoutDirection = direction; + } inline bool hasNewLayout() const { return mHasNewLayout; } @@ -1114,6 +1152,38 @@ namespace WeexCore { return ret; } + void markInheritableDirty() { + if (resetInheritableSet()) { + // if some style was inherited from parent, reset those styles + // then mark self dirty + markDirty(false); + + // traverse children to mark dirty + if(getChildCount() == 0){ + return; + } + else { + for (auto it = ChildListIterBegin(); it != ChildListIterEnd(); it++) { + (*it)->markInheritableDirty(); + } + } + } + } + + /** + * if some style was inherited from parent, reset those styles, then return true, eles return false + */ + bool resetInheritableSet() { + if (mCssStyle == nullptr || mLayoutResult == nullptr) return false; + + bool hasInheritedStyle = false; + if (mCssStyle->mDirection == kDirectionInherit) { + mLayoutResult->mLayoutDirection = kDirectionInherit; + hasInheritedStyle = true; + } + return hasInheritedStyle; + } + inline void setHasNewLayout(const bool hasNewLayout) { this->mHasNewLayout = hasNewLayout; } diff --git a/weex_core/Source/core/layout/style.h b/weex_core/Source/core/layout/style.h index a5c5f7eaa8..5c9ca89a24 100644 --- a/weex_core/Source/core/layout/style.h +++ b/weex_core/Source/core/layout/style.h @@ -201,6 +201,8 @@ namespace WeexCore { WXCoreAlignSelf mAlignSelf; WXCorePositionType mPositionType; + + WXCoreDirection mDirection; float mFlexGrow; @@ -242,7 +244,8 @@ namespace WeexCore { constexpr static WXCorePositionType kWXCorePositionTypeDefault = kRelative; - WXCoreCSSStyle() : mFlexDirection(kFlexDirectionDefault), + WXCoreCSSStyle() : mDirection(kDirectionInherit), + mFlexDirection(kFlexDirectionDefault), mFlexWrap(kFlexWrapDefault), mJustifyContent(kFlexJustifyContentDefault), mAlignItems(kFlexAlignItemsDefault), @@ -257,6 +260,7 @@ namespace WeexCore { } ~WXCoreCSSStyle() { + mDirection = kDirectionInherit; mFlexDirection = kFlexDirectionDefault; mFlexWrap = kFlexWrapDefault; mJustifyContent = kFlexJustifyContentDefault; diff --git a/weex_core/Source/core/render/node/render_object.cpp b/weex_core/Source/core/render/node/render_object.cpp index ffc8388f2b..ba547a79dd 100644 --- a/weex_core/Source/core/render/node/render_object.cpp +++ b/weex_core/Source/core/render/node/render_object.cpp @@ -160,6 +160,13 @@ StyleType RenderObject::ApplyStyle(const std::string &key, } } return kTypeLayout; + } else if (key == DIRECTION) { + WeexCore::WXCoreDirection direction = GetWXCoreDirection(value); + if (direction == WeexCore::kDirectionInherit && this->is_root_render_ ) { + direction = WeexCore::kDirectionLTR; + } + setDirection(direction, updating); + return kTypeInheritableLayout; } else if (key == FLEX_DIRECTION) { setFlexDirection(GetWXCoreFlexDirection(value), updating); return kTypeLayout; diff --git a/weex_core/Source/core/render/node/render_object.h b/weex_core/Source/core/render/node/render_object.h index 5242a641ca..9522d9d7fe 100644 --- a/weex_core/Source/core/render/node/render_object.h +++ b/weex_core/Source/core/render/node/render_object.h @@ -44,7 +44,8 @@ typedef enum StyleType { kTypeLayout, kTypeMargin, kTypePadding, - kTypeBorder + kTypeBorder, + kTypeInheritableLayout } StyleType; class RenderObject : public IRenderObject { diff --git a/weex_core/Source/core/render/node/render_scroller.cpp b/weex_core/Source/core/render/node/render_scroller.cpp index 05e67e2e17..03d0b8d4a5 100644 --- a/weex_core/Source/core/render/node/render_scroller.cpp +++ b/weex_core/Source/core/render/node/render_scroller.cpp @@ -52,4 +52,11 @@ void RenderScroller::set_flex(const float flex) { this->is_set_flex_ = true; WXCoreLayoutNode::set_flex(flex); } + + void RenderScroller::onLayout(const float left, const float top, const float right, const float bottom, + WXCoreLayoutNode *const absoulteItem, WXCoreFlexLine *const flexLine) { + // In scroller only use left to right direction to caculate children frame + this->setLayoutDirection(kDirectionLTR); + RenderObject::onLayout(left, top, right, bottom, absoulteItem, flexLine); + } } // namespace WeexCore diff --git a/weex_core/Source/core/render/node/render_scroller.h b/weex_core/Source/core/render/node/render_scroller.h index 59a35dff41..1cf6fc6389 100644 --- a/weex_core/Source/core/render/node/render_scroller.h +++ b/weex_core/Source/core/render/node/render_scroller.h @@ -37,6 +37,24 @@ class RenderScroller : public RenderObject { const float ¤t_length) const override { return NAN; } + +protected: + void onLayout(const float left, const float top, const float right, const float bottom, + WXCoreLayoutNode *const absoulteItem, WXCoreFlexLine *const flexLine) override; + + // Since scroll only use ltr to layout children actually, + // so we need override this method to return calculated inherit direction as normal render_object do + inline WXCoreDirection getLayoutDirection() const override { + WXCoreDirection styleDirection = this->getDirection(); + if (styleDirection != kDirectionInherit) { + return styleDirection; + } else if (this->getParent() != nullptr) { + WXCoreLayoutNode *parent = this->getParent(); + return parent->getLayoutDirection(); + } + return WEEXCORE_CSS_DEFAULT_DIRECTION; + } + }; } // namespace WeexCore #endif // CORE_RENDER_NODE_RENDER_SCROLLER_H_ diff --git a/weex_core/Source/core/render/page/render_page.cpp b/weex_core/Source/core/render/page/render_page.cpp index d54fadee35..875cd3de09 100644 --- a/weex_core/Source/core/render/page/render_page.cpp +++ b/weex_core/Source/core/render/page/render_page.cpp @@ -221,7 +221,8 @@ bool RenderPage::UpdateStyle( std::vector<std::pair<std::string, std::string>> *margin = nullptr; std::vector<std::pair<std::string, std::string>> *padding = nullptr; std::vector<std::pair<std::string, std::string>> *border = nullptr; - + bool inheriableLayout = false; + bool flag = false; int result = WeexCoreManager::Instance() @@ -277,13 +278,16 @@ bool RenderPage::UpdateStyle( flag = true; }); break; + case kTypeInheritableLayout: + inheriableLayout = true; + break; default: break; } } } if (style != nullptr || margin != nullptr || padding != nullptr || - border != nullptr) + border != nullptr || inheriableLayout) SendUpdateStyleAction(render, style, margin, padding, border); Batch(); diff --git a/weex_core/release.sh b/weex_core/release.sh old mode 100644 new mode 100755 ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org > feature for Rtl layout > ---------------------- > > Key: WEEX-653 > URL: https://issues.apache.org/jira/browse/WEEX-653 > Project: Weex > Issue Type: New Feature > Components: Android, iOS, Web Renderer > Affects Versions: 0.18 > Reporter: Kai Tian > Assignee: YorkShen > Priority: Major > Fix For: 0.18 > > > [android][ios][core] Whole platform support rtl direction by CSS > "direction:rtl" > [core] Update the new layout engine to support inheritable CSS > "direction:rtl". > [ios][android] Update component layer, to support list, slide, scroller RTL > direction. Update WXEnvironment, now we can get system layout direction by > WXEnvironment.layoutDirection. Update WXDomModule, now we can get element > layout direction by getLayoutDirection(ref,callback) > Few languages such as Arabic, Hebrew, or Persian are written from Right to > Left, but weex not support RTL layouts. To handle them. Since this PR merged, > we can use weex for RTL languages in both android and ios. And Scroller > performance like native, layout and scroll direction will be reverse. > > There are some test Demos: > Div+Scroller http://dotwe.org/vue/9c3ee9824dd49111a87a93ea6513039a > Horizontal Scroller http://dotwe.org/vue/0dd282e3183c2384ec38a465c915ce41 > Scroller (ScrollToElement) > http://dotwe.org/vue/a24001978199df6e981673e9460598c8 > Slider http://dotwe.org/vue/7e45bcf71ad396032944bc51541d6350 > Animation http://dotwe.org/vue/b578facdbb623f6922f0905ae09b7e49 > envDirection + getLayoutDirection > http://dotwe.org/vue/d6c5cf7a2a907c33cf4ba25fea6e6ef4 -- This message was sent by Atlassian JIRA (v7.6.3#76005)