diff --git a/Android/app/build.gradle b/Android/app/build.gradle index 594d0dc6..0d7367c1 100644 --- a/Android/app/build.gradle +++ b/Android/app/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion 29 buildToolsVersion '29.0.2' defaultConfig { - applicationId "com.github.penfeizhou.doric" + applicationId "pub.doric.demo" minSdkVersion 16 targetSdkVersion 29 versionCode 1 diff --git a/Android/doric/src/main/java/pub/doric/shader/ImageNode.java b/Android/doric/src/main/java/pub/doric/shader/ImageNode.java index 4c397afc..690573d3 100644 --- a/Android/doric/src/main/java/pub/doric/shader/ImageNode.java +++ b/Android/doric/src/main/java/pub/doric/shader/ImageNode.java @@ -19,7 +19,7 @@ import android.graphics.drawable.Drawable; import androidx.annotation.Nullable; -import android.view.ViewGroup; +import android.text.TextUtils; import android.widget.ImageView; import com.bumptech.glide.Glide; @@ -31,7 +31,7 @@ import com.bumptech.glide.request.target.Target; import pub.doric.DoricContext; import pub.doric.extension.bridge.DoricPlugin; -import com.github.pengfeizhou.jscore.JSObject; +import com.github.pengfeizhou.jscore.JSONBuilder; import com.github.pengfeizhou.jscore.JSValue; /** @@ -41,6 +41,8 @@ import com.github.pengfeizhou.jscore.JSValue; */ @DoricPlugin(name = "Image") public class ImageNode extends ViewNode { + private String loadCallbackId = ""; + public ImageNode(DoricContext doricContext) { super(doricContext); } @@ -52,22 +54,51 @@ public class ImageNode extends ViewNode { @Override protected void blend(ImageView view, String name, JSValue prop) { - if ("imageUrl".equals(name)) { - Glide.with(getContext()).load(prop.asString().value()) - .listener(new RequestListener() { - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - return false; - } + switch (name) { + case "imageUrl": + Glide.with(getContext()).load(prop.asString().value()) + .listener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + if (!TextUtils.isEmpty(loadCallbackId)) { + callJSResponse(loadCallbackId); + } + return false; + } - @Override - public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - return false; - } - }) - .into(view); - } else { - super.blend(view, name, prop); + @Override + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + if (!TextUtils.isEmpty(loadCallbackId)) { + callJSResponse(loadCallbackId, new JSONBuilder() + .put("width", resource.getIntrinsicWidth()) + .put("height", resource.getIntrinsicHeight()) + .toJSONObject()); + } + return false; + } + }) + .into(view); + break; + case "scaleType": + int scaleType = prop.asNumber().toInt(); + switch (scaleType) { + case 1: + view.setScaleType(ImageView.ScaleType.FIT_CENTER); + break; + case 2: + view.setScaleType(ImageView.ScaleType.CENTER_CROP); + break; + default: + view.setScaleType(ImageView.ScaleType.FIT_XY); + break; + } + break; + case "loadCallback": + this.loadCallbackId = prop.asString().value(); + break; + default: + super.blend(view, name, prop); + break; } } } diff --git a/Android/doric/src/main/java/pub/doric/shader/TextNode.java b/Android/doric/src/main/java/pub/doric/shader/TextNode.java index 246a16f9..78f95906 100644 --- a/Android/doric/src/main/java/pub/doric/shader/TextNode.java +++ b/Android/doric/src/main/java/pub/doric/shader/TextNode.java @@ -57,6 +57,8 @@ public class TextNode extends ViewNode { case "textAlignment": view.setGravity(prop.asNumber().toInt() | Gravity.CENTER_VERTICAL); break; + case "numberOfLines": + break; default: super.blend(view, name, prop); break; diff --git a/Android/doric/src/main/java/pub/doric/widget/HVScrollView.java b/Android/doric/src/main/java/pub/doric/widget/HVScrollView.java index f74db4e8..7b86cd49 100644 --- a/Android/doric/src/main/java/pub/doric/widget/HVScrollView.java +++ b/Android/doric/src/main/java/pub/doric/widget/HVScrollView.java @@ -135,12 +135,6 @@ public class HVScrollView extends FrameLayout implements NestedScrollingParent, */ private VelocityTracker mVelocityTracker; - /** - * When set to true, the scroll view measure its child to make it fill the currently - * visible area. - */ - private boolean mFillViewport; - /** * Whether arrow scrolling is animated. */ @@ -203,9 +197,6 @@ public class HVScrollView extends FrameLayout implements NestedScrollingParent, final TypedArray a = context.obtainStyledAttributes( attrs, SCROLLVIEW_STYLEABLE, defStyleAttr, 0); - - setFillViewport(a.getBoolean(0, false)); - a.recycle(); mParentHelper = new NestedScrollingParentHelper(this); @@ -513,30 +504,6 @@ public class HVScrollView extends FrameLayout implements NestedScrollingParent, return false; } - /** - * Indicates whether this ScrollView's content is stretched to fill the viewport. - * - * @return True if the content fills the viewport, false otherwise. - * @attr name android:fillViewport - */ - public boolean isFillViewport() { - return mFillViewport; - } - - /** - * Set whether this ScrollView should stretch its content height to fill the viewport or not. - * - * @param fillViewport True to stretch the content's height to the viewport's - * boundaries, false otherwise. - * @attr name android:fillViewport - */ - public void setFillViewport(boolean fillViewport) { - if (fillViewport != mFillViewport) { - mFillViewport = fillViewport; - requestLayout(); - } - } - /** * @return Whether arrow scrolling will animate its transition. */ @@ -566,10 +533,6 @@ public class HVScrollView extends FrameLayout implements NestedScrollingParent, protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (!mFillViewport) { - return; - } - final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (heightMode == MeasureSpec.UNSPECIFIED && widthMode == MeasureSpec.UNSPECIFIED) { @@ -587,14 +550,17 @@ public class HVScrollView extends FrameLayout implements NestedScrollingParent, int childWidthMeasureSpec; int childHeightMeasureSpec; final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (child.getMeasuredWidth() < width) { + if (lp.width != ViewGroup.LayoutParams.MATCH_PARENT && lp.height != ViewGroup.LayoutParams.MATCH_PARENT) { + return; + } + if (child.getMeasuredWidth() < width && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); } else { widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight(), lp.width); } - if (child.getMeasuredHeight() < height) { + if (child.getMeasuredHeight() < height && lp.height == ViewGroup.LayoutParams.MATCH_PARENT) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); } else { heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); diff --git a/demo/index.ts b/demo/index.ts index 432ed215..bd38e707 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -6,4 +6,5 @@ export default [ 'src/SliderDemo', 'src/LayoutDemo', 'src/EffectsDemo', + 'src/ImageDemo', ] \ No newline at end of file diff --git a/demo/src/EffectsDemo.ts b/demo/src/EffectsDemo.ts index 71780949..355a8350 100644 --- a/demo/src/EffectsDemo.ts +++ b/demo/src/EffectsDemo.ts @@ -1,6 +1,6 @@ import { Group, Panel, Text, text, gravity, Color, Stack, LayoutSpec, vlayout, hlayout, scroller, IVLayout, IHLayout, layoutConfig } from "doric"; -import { colors } from "./colorutils"; +import { colors } from "./utils"; function box(idx = 0) { @@ -361,7 +361,7 @@ class EffectsDemo extends Panel { it.space = 20 }), ).also(it => { - it.layoutConfig = layoutConfig().wrap() + it.layoutConfig = layoutConfig().atmost() }).in(rootView) } } \ No newline at end of file diff --git a/demo/src/ImageDemo.ts b/demo/src/ImageDemo.ts new file mode 100644 index 00000000..5e155cc5 --- /dev/null +++ b/demo/src/ImageDemo.ts @@ -0,0 +1,64 @@ +import { Group, Panel, List, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout, Gravity, hlayout, Text, scroller, layoutConfig, image, IView, IVLayout, ScaleType } from "doric"; +import { colors, label } from "./utils"; +const imageUrl = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574244792703&di=c49ed8cd284c367fa8f00065a85428bd&imgtype=0&src=http%3A%2F%2Fimg3.iqilu.com%2Fdata%2Fattachment%2Fforum%2F201308%2F21%2F201709zikkhkjh7dgfi9f0.jpg' +@Entry +class ImageDemo extends Panel { + build(rootView: Group): void { + scroller(vlayout([ + text({ + text: "Image Demo", + layoutConfig: layoutConfig().w(LayoutSpec.AT_MOST), + textSize: 30, + textColor: Color.WHITE, + bgColor: colors[1], + textAlignment: gravity().center(), + height: 50, + }), + label('ScaleToFill'), + image({ + imageUrl, + width: 300, + height: 300, + border: { + width: 2, + color: Color.GRAY, + }, + scaleType: ScaleType.ScaleToFill, + layoutConfig: layoutConfig().exactly(), + loadCallback: (ret) => { + log('loadCallback', ret) + } + }), + label('ScaleAspectFit'), + image({ + imageUrl, + width: 300, + height: 300, + border: { + width: 2, + color: Color.GRAY, + }, + scaleType: ScaleType.ScaleAspectFit, + layoutConfig: layoutConfig().exactly(), + }), + label('ScaleAspectFill'), + image({ + imageUrl, + width: 300, + height: 300, + border: { + width: 2, + color: Color.GRAY, + }, + scaleType: ScaleType.ScaleAspectFill, + layoutConfig: layoutConfig().exactly(), + }), + ]).apply({ + layoutConfig: layoutConfig().atmost().h(LayoutSpec.WRAP_CONTENT), + gravity: gravity().center(), + space: 10, + } as IVLayout)).apply({ + layoutConfig: layoutConfig().atmost(), + }).in(rootView) + } +} \ No newline at end of file diff --git a/demo/src/SliderDemo.ts b/demo/src/SliderDemo.ts index a1f6a35c..09cb47db 100644 --- a/demo/src/SliderDemo.ts +++ b/demo/src/SliderDemo.ts @@ -1,11 +1,13 @@ import { Group, Panel, List, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout, Gravity, hlayout, slider, slideItem, image, layoutConfig } from "doric"; -import { colors } from "./colorutils"; +import { colors } from "./utils"; const imageUrls = [ 'http://b.hiphotos.baidu.com/image/pic/item/908fa0ec08fa513db777cf78376d55fbb3fbd9b3.jpg', 'http://f.hiphotos.baidu.com/image/pic/item/0e2442a7d933c8956c0e8eeadb1373f08202002a.jpg', 'http://f.hiphotos.baidu.com/image/pic/item/b151f8198618367aa7f3cc7424738bd4b31ce525.jpg', 'http://b.hiphotos.baidu.com/image/pic/item/0eb30f2442a7d9337119f7dba74bd11372f001e0.jpg', + 'http://a.hiphotos.baidu.com/image/h%3D300/sign=b38f3fc35b0fd9f9bf175369152cd42b/9a504fc2d5628535bdaac29e9aef76c6a6ef63c2.jpg', + 'http://h.hiphotos.baidu.com/image/pic/item/810a19d8bc3eb1354c94a704ac1ea8d3fd1f4439.jpg', 'http://calonye.com/wp-content/uploads/2015/08/0-wx_fmtgiftpwebpwxfrom5wx_lazy1-9.gif', 'http://hbimg.b0.upaiyun.com/ca29ea125b7f2d580f573e48eb594b1ef509757f34a08-m0hK45_fw658', 'https://misc.aotu.io/ONE-SUNDAY/SteamEngine.png', @@ -31,7 +33,7 @@ class SliderPanel extends Panel { renderPage: (idx) => { return slideItem(image({ imageUrl: imageUrls[idx % imageUrls.length], - layoutConfig: layoutConfig().wrap().a(gravity().center()), + layoutConfig: layoutConfig().w(LayoutSpec.WRAP_CONTENT).h(LayoutSpec.WRAP_CONTENT).a(gravity().center()), })).also(it => { let start = idx it.onClick = () => { diff --git a/demo/src/colorutils.ts b/demo/src/colorutils.ts deleted file mode 100644 index 093d46c3..00000000 --- a/demo/src/colorutils.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Color } from "doric"; - -export const colors = [ - "#70a1ff", - "#7bed9f", - "#ff6b81", - "#a4b0be", - "#f0932b", - "#eb4d4b", - "#6ab04c", - "#e056fd", - "#686de0", - "#30336b", -].map(e => Color.parse(e)) \ No newline at end of file diff --git a/demo/src/utils.ts b/demo/src/utils.ts new file mode 100644 index 00000000..29f77355 --- /dev/null +++ b/demo/src/utils.ts @@ -0,0 +1,36 @@ +import { Color, text, Stack, Text } from "doric"; + +export const colors = [ + "#70a1ff", + "#7bed9f", + "#ff6b81", + "#a4b0be", + "#f0932b", + "#eb4d4b", + "#6ab04c", + "#e056fd", + "#686de0", + "#30336b", +].map(e => Color.parse(e)) + +export function label(str: string) { + return text({ + text: str, + textSize: 16, + }) +} + +export function box(idx = 0) { + return (new Stack).also(it => { + it.width = it.height = 20 + it.bgColor = colors[idx || 0] + }) +} +export function boxStr(str: string, idx = 0) { + return (new Text).also(it => { + it.width = it.height = 20 + it.text = str + it.textColor = Color.WHITE + it.bgColor = colors[idx || 0] + }) +} \ No newline at end of file diff --git a/iOS/Example/Example.xcodeproj/project.pbxproj b/iOS/Example/Example.xcodeproj/project.pbxproj index 13db7cbe..66134c6b 100644 --- a/iOS/Example/Example.xcodeproj/project.pbxproj +++ b/iOS/Example/Example.xcodeproj/project.pbxproj @@ -153,8 +153,8 @@ E2334AF222E9D2060098A085 /* ViewController.m */, E2334AF422E9D2060098A085 /* Main.storyboard */, E2334AF722E9D2070098A085 /* Assets.xcassets */, - E2334AF922E9D2070098A085 /* LaunchScreen.storyboard */, E2334AFC22E9D2070098A085 /* Info.plist */, + E2334AF922E9D2070098A085 /* LaunchScreen.storyboard */, E2334AFD22E9D2070098A085 /* main.m */, D751D19E97EF4EDD7588FEBE /* DemoVC.m */, D751DDEC114E037231257E64 /* DemoVC.h */, diff --git a/iOS/Example/Example/Info.plist b/iOS/Example/Example/Info.plist index 16be3b68..f3caaf95 100644 --- a/iOS/Example/Example/Info.plist +++ b/iOS/Example/Example/Info.plist @@ -34,6 +34,13 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + LSApplicationCategoryType + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait diff --git a/iOS/Pod/Classes/Shader/DoricImageNode.m b/iOS/Pod/Classes/Shader/DoricImageNode.m index 56b0d707..dfa204ef 100644 --- a/iOS/Pod/Classes/Shader/DoricImageNode.m +++ b/iOS/Pod/Classes/Shader/DoricImageNode.m @@ -21,21 +21,57 @@ // #import "DoricImageNode.h" +#import "Doric.h" #import +@interface DoricImageNode () +@property(nonatomic, copy) NSString *loadCallbackId; +@end + @implementation DoricImageNode - (UIImageView *)build { - return [[UIImageView alloc] init]; + return [[UIImageView new] also:^(UIImageView *it) { + it.clipsToBounds = YES; + }]; } - (void)blendView:(UIImageView *)view forPropName:(NSString *)name propValue:(id)prop { - if ([name isEqualToString:@"imageUrl"]) { + if ([@"imageUrl" isEqualToString:name]) { __weak typeof(self) _self = self; [view sd_setImageWithURL:[NSURL URLWithString:prop] completed:^(UIImage *_Nullable image, NSError *_Nullable error, SDImageCacheType cacheType, NSURL *_Nullable imageURL) { __strong typeof(_self) self = _self; - [self requestLayout]; + if (error) { + if (self.loadCallbackId.length > 0) { + [self callJSResponse:self.loadCallbackId, nil]; + } + } else { + if (self.loadCallbackId.length > 0) { + [self callJSResponse:self.loadCallbackId, + @{@"width": @(image.size.width), @"height": @(image.size.height)}, + nil]; + } + [self requestLayout]; + } + }]; + } else if ([@"scaleType" isEqualToString:name]) { + switch ([prop integerValue]) { + case 1: { + self.view.contentMode = UIViewContentModeScaleAspectFit; + break; + } + case 2: { + self.view.contentMode = UIViewContentModeScaleAspectFill; + break; + } + default: { + self.view.contentMode = UIViewContentModeScaleToFill; + break; + } + } + } else if ([@"loadCallback" isEqualToString:name]) { + self.loadCallbackId = prop; } else { [super blendView:view forPropName:name propValue:prop]; } diff --git a/iOS/Pod/Classes/Shader/DoricSlideItemNode.m b/iOS/Pod/Classes/Shader/DoricSlideItemNode.m index ff840a5c..a65cb62e 100644 --- a/iOS/Pod/Classes/Shader/DoricSlideItemNode.m +++ b/iOS/Pod/Classes/Shader/DoricSlideItemNode.m @@ -39,6 +39,7 @@ - (instancetype)initWithContext:(DoricContext *)doricContext { - (void)initWithSuperNode:(DoricSuperNode *)superNode { [super initWithSuperNode:superNode]; self.reusable = YES; + self.view.clipsToBounds = YES; } - (DoricStackView *)build { diff --git a/iOS/Pod/Classes/Shader/DoricSuperNode.m b/iOS/Pod/Classes/Shader/DoricSuperNode.m index 4f4b73d8..ffcd08ea 100644 --- a/iOS/Pod/Classes/Shader/DoricSuperNode.m +++ b/iOS/Pod/Classes/Shader/DoricSuperNode.m @@ -152,4 +152,7 @@ - (void)clearSubModel { - (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { return nil; } +- (void)requestLayout { + [self.view setNeedsLayout]; +} @end \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricTextNode.m b/iOS/Pod/Classes/Shader/DoricTextNode.m index f73e980f..9b54525c 100644 --- a/iOS/Pod/Classes/Shader/DoricTextNode.m +++ b/iOS/Pod/Classes/Shader/DoricTextNode.m @@ -23,10 +23,13 @@ #import "DoricTextNode.h" #import "DoricUtil.h" #import "DoricGroupNode.h" +#import "Doric.h" @implementation DoricTextNode - (UILabel *)build { - return [[UILabel alloc] init]; + return [[[UILabel alloc] init] also:^(UILabel *it) { + it.textAlignment = NSTextAlignmentCenter; + }]; } - (void)blendView:(UILabel *)view forPropName:(NSString *)name propValue:(id)prop { diff --git a/js-framework/src/ui/widgets.ts b/js-framework/src/ui/widgets.ts index 0bd815d6..6790b604 100644 --- a/js-framework/src/ui/widgets.ts +++ b/js-framework/src/ui/widgets.ts @@ -42,11 +42,25 @@ export class Text extends View implements IText { textAlignment?: Gravity } +export enum ScaleType { + ScaleToFill = 0, + ScaleAspectFit, + ScaleAspectFill, +} + export interface IImage extends IView { imageUrl?: string + scaleType?: ScaleType + loadCallback?: (image: { width: number; height: number } | undefined) => void } export class Image extends View implements IImage { @Property imageUrl?: string + + @Property + scaleType?: ScaleType + + @Property + loadCallback?: (image: { width: number; height: number } | undefined) => void } \ No newline at end of file