Merge branch 'feature/listview' into 'master'
Feature/listview See merge request !10
This commit is contained in:
commit
19c700705b
@ -22,15 +22,16 @@ import android.widget.FrameLayout;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import pub.doric.dev.DevPanel;
|
||||
import pub.doric.DoricContext;
|
||||
import pub.doric.DoricDriver;
|
||||
import pub.doric.dev.DevPanel;
|
||||
import pub.doric.dev.LocalServer;
|
||||
import pub.doric.dev.event.EnterDebugEvent;
|
||||
import pub.doric.dev.event.QuitDebugEvent;
|
||||
@ -49,9 +50,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
doricContext = DoricContext.create(this, DoricUtils.readAssetFile("demo/Snake.js"), "test");
|
||||
doricContext = DoricContext.create(this, DoricUtils.readAssetFile("demo/ListDemo.js"), "test");
|
||||
doricContext.init(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
// doricContext.callEntity("log");
|
||||
doricContext.getRootNode().setRootView((FrameLayout) findViewById(R.id.root));
|
||||
|
||||
LocalServer localServer = new LocalServer(getApplicationContext(), 8910);
|
||||
|
@ -20,6 +20,8 @@ import android.text.TextUtils;
|
||||
import pub.doric.plugin.ShaderPlugin;
|
||||
import pub.doric.shader.HLayoutNode;
|
||||
import pub.doric.shader.ImageNode;
|
||||
import pub.doric.shader.list.ListItemNode;
|
||||
import pub.doric.shader.list.ListNode;
|
||||
import pub.doric.shader.RootNode;
|
||||
import pub.doric.shader.StackNode;
|
||||
import pub.doric.shader.TextNode;
|
||||
@ -66,6 +68,8 @@ public class DoricRegistry {
|
||||
this.registerViewNode(StackNode.class);
|
||||
this.registerViewNode(VLayoutNode.class);
|
||||
this.registerViewNode(HLayoutNode.class);
|
||||
this.registerViewNode(ListNode.class);
|
||||
this.registerViewNode(ListItemNode.class);
|
||||
initRegistry(this);
|
||||
}
|
||||
|
||||
|
@ -64,6 +64,15 @@ public class SettableFuture<T> {
|
||||
return mResult;
|
||||
}
|
||||
|
||||
public T get() {
|
||||
try {
|
||||
mReadyLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return mResult;
|
||||
}
|
||||
|
||||
public static class TimeoutException extends RuntimeException {
|
||||
|
||||
public TimeoutException() {
|
||||
|
@ -197,7 +197,7 @@ public class DoricJSEngine implements Handler.Callback, DoricTimerExtension.Time
|
||||
values.add(DoricUtils.toJavaValue(arg));
|
||||
}
|
||||
return mDoricJSE.invokeMethod(DoricConstant.GLOBAL_DORIC, method,
|
||||
values.toArray(new JavaValue[values.size()]), true);
|
||||
values.toArray(new JavaValue[values.size()]), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,15 +1,14 @@
|
||||
package pub.doric.engine.remote;
|
||||
|
||||
import com.github.pengfeizhou.jscore.JSDecoder;
|
||||
import com.github.pengfeizhou.jscore.JSONBuilder;
|
||||
import com.github.pengfeizhou.jscore.JavaFunction;
|
||||
import com.github.pengfeizhou.jscore.JavaValue;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.EOFException;
|
||||
@ -25,14 +24,10 @@ import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
import pub.doric.dev.event.QuitDebugEvent;
|
||||
import pub.doric.utils.DoricUtils;
|
||||
|
||||
public class RemoteJSExecutor {
|
||||
|
||||
private final WebSocket webSocket;
|
||||
|
||||
private final Map<String, JavaFunction> globalFunctions = new HashMap<>();
|
||||
|
||||
private JSDecoder temp;
|
||||
|
||||
public RemoteJSExecutor() {
|
||||
@ -41,7 +36,7 @@ public class RemoteJSExecutor {
|
||||
.readTimeout(10, TimeUnit.SECONDS)
|
||||
.writeTimeout(10, TimeUnit.SECONDS)
|
||||
.build();
|
||||
final Request request = new Request.Builder().url("ws://192.168.25.175:2080").build();
|
||||
final Request request = new Request.Builder().url("ws://192.168.24.79:2080").build();
|
||||
|
||||
final Thread current = Thread.currentThread();
|
||||
webSocket = okHttpClient.newWebSocket(request, new WebSocketListener() {
|
||||
@ -67,55 +62,28 @@ public class RemoteJSExecutor {
|
||||
|
||||
@Override
|
||||
public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
|
||||
JsonElement je = DoricUtils.gson.fromJson(text, JsonElement.class);
|
||||
|
||||
if (je instanceof JsonObject) {
|
||||
JsonObject jo = ((JsonObject) je);
|
||||
String cmd = jo.get("cmd").getAsString();
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject(text);
|
||||
String cmd = jsonObject.optString("cmd");
|
||||
switch (cmd) {
|
||||
case "injectGlobalJSFunction": {
|
||||
String name = jo.get("name").getAsString();
|
||||
JsonArray arguments = jo.getAsJsonArray("arguments");
|
||||
JSDecoder[] decoders = new JSDecoder[arguments.size()];
|
||||
for (int i = 0; i < arguments.size(); i++) {
|
||||
if (arguments.get(i).isJsonPrimitive()) {
|
||||
JsonPrimitive jp = ((JsonPrimitive) arguments.get(i));
|
||||
if (jp.isNumber()) {
|
||||
ValueBuilder vb = new ValueBuilder(jp.getAsNumber());
|
||||
JSDecoder decoder = new JSDecoder(vb.build());
|
||||
decoders[i] = decoder;
|
||||
} else if (jp.isBoolean()) {
|
||||
ValueBuilder vb = new ValueBuilder(jp.getAsBoolean());
|
||||
JSDecoder decoder = new JSDecoder(vb.build());
|
||||
decoders[i] = decoder;
|
||||
} else if (jp.isString()) {
|
||||
ValueBuilder vb = new ValueBuilder(jp.getAsString());
|
||||
JSDecoder decoder = new JSDecoder(vb.build());
|
||||
decoders[i] = decoder;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
ValueBuilder vb = new ValueBuilder(new JSONObject(DoricUtils.gson.toJson(arguments.get(i))));
|
||||
JSDecoder decoder = new JSDecoder(vb.build());
|
||||
decoders[i] = decoder;
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
String name = jsonObject.optString("name");
|
||||
JSONArray arguments = jsonObject.optJSONArray("arguments");
|
||||
assert arguments != null;
|
||||
JSDecoder[] decoders = new JSDecoder[arguments.length()];
|
||||
for (int i = 0; i < arguments.length(); i++) {
|
||||
Object o = arguments.get(i);
|
||||
decoders[i] = new JSDecoder(new ValueBuilder(o).build());
|
||||
}
|
||||
|
||||
|
||||
globalFunctions.get(name).exec(decoders);
|
||||
}
|
||||
|
||||
break;
|
||||
case "invokeMethod": {
|
||||
try {
|
||||
Object result = jo.get("result");
|
||||
Object result = jsonObject.opt("result");
|
||||
ValueBuilder vb = new ValueBuilder(result);
|
||||
JSDecoder decoder = new JSDecoder(vb.build());
|
||||
temp = decoder;
|
||||
temp = new JSDecoder(vb.build());
|
||||
System.out.println(result);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
@ -125,6 +93,8 @@ public class RemoteJSExecutor {
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -141,33 +111,29 @@ public class RemoteJSExecutor {
|
||||
|
||||
public void injectGlobalJSFunction(String name, JavaFunction javaFunction) {
|
||||
globalFunctions.put(name, javaFunction);
|
||||
|
||||
JsonObject jo = new JsonObject();
|
||||
jo.addProperty("cmd", "injectGlobalJSFunction");
|
||||
jo.addProperty("name", name);
|
||||
webSocket.send(DoricUtils.gson.toJson(jo));
|
||||
webSocket.send(new JSONBuilder().put("cmd", "injectGlobalJSFunction")
|
||||
.put("name", name).toString()
|
||||
);
|
||||
}
|
||||
|
||||
public void injectGlobalJSObject(String name, JavaValue javaValue) {
|
||||
}
|
||||
|
||||
public JSDecoder invokeMethod(String objectName, String functionName, JavaValue[] javaValues, boolean hashKey) {
|
||||
JsonObject jo = new JsonObject();
|
||||
jo.addProperty("cmd", "invokeMethod");
|
||||
jo.addProperty("objectName", objectName);
|
||||
jo.addProperty("functionName", functionName);
|
||||
|
||||
JsonArray ja = new JsonArray();
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
for (JavaValue javaValue : javaValues) {
|
||||
JsonObject argument = new JsonObject();
|
||||
argument.addProperty("type", javaValue.getType());
|
||||
argument.addProperty("value", javaValue.getValue());
|
||||
ja.add(argument);
|
||||
jsonArray.put(new JSONBuilder()
|
||||
.put("type", javaValue.getType())
|
||||
.put("value", javaValue.getValue())
|
||||
.toJSONObject());
|
||||
}
|
||||
jo.add("javaValues", ja);
|
||||
|
||||
jo.addProperty("hashKey", hashKey);
|
||||
webSocket.send(DoricUtils.gson.toJson(jo));
|
||||
webSocket.send(new JSONBuilder()
|
||||
.put("cmd", "invokeMethod")
|
||||
.put("objectName", objectName)
|
||||
.put("functionName", functionName)
|
||||
.put("javaValues", jsonArray)
|
||||
.put("hashKey", hashKey)
|
||||
.toString());
|
||||
|
||||
LockSupport.park(Thread.currentThread());
|
||||
return temp;
|
||||
|
@ -16,6 +16,7 @@
|
||||
package pub.doric.plugin;
|
||||
|
||||
import pub.doric.DoricContext;
|
||||
import pub.doric.async.AsyncResult;
|
||||
import pub.doric.extension.bridge.DoricMethod;
|
||||
import pub.doric.extension.bridge.DoricPlugin;
|
||||
import pub.doric.utils.DoricLog;
|
||||
@ -49,7 +50,23 @@ public class ShaderPlugin extends DoricJavaPlugin {
|
||||
rootNode.render(jsObject.getProperty("props").asObject());
|
||||
return null;
|
||||
}
|
||||
}, ThreadMode.UI);
|
||||
}, ThreadMode.UI).setCallback(new AsyncResult.Callback<Object>() {
|
||||
@Override
|
||||
public void onResult(Object result) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t) {
|
||||
t.printStackTrace();
|
||||
DoricLog.e("Shader.render:error%s", t.getLocalizedMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
DoricLog.e("Shader.render:error%s", e.getLocalizedMessage());
|
||||
|
@ -15,159 +15,140 @@
|
||||
*/
|
||||
package pub.doric.shader;
|
||||
|
||||
import android.util.SparseArray;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import pub.doric.DoricContext;
|
||||
import pub.doric.utils.DoricUtils;
|
||||
|
||||
import com.github.pengfeizhou.jscore.JSArray;
|
||||
import com.github.pengfeizhou.jscore.JSObject;
|
||||
import com.github.pengfeizhou.jscore.JSValue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Description: com.github.penfeizhou.doric.widget
|
||||
* @Author: pengfei.zhou
|
||||
* @CreateDate: 2019-07-20
|
||||
*/
|
||||
public abstract class GroupNode<F extends ViewGroup> extends ViewNode<F> {
|
||||
private Map<String, ViewNode> mChildrenNode = new HashMap<>();
|
||||
private SparseArray<ViewNode> mIndexInfo = new SparseArray<>();
|
||||
public abstract class GroupNode<F extends ViewGroup> extends SuperNode<F> {
|
||||
private ArrayList<ViewNode> mChildNodes = new ArrayList<>();
|
||||
private ArrayList<String> mChildViewIds = new ArrayList<>();
|
||||
|
||||
protected boolean mReusable = false;
|
||||
|
||||
public GroupNode(DoricContext doricContext) {
|
||||
super(doricContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blend(F view, ViewGroup.LayoutParams layoutParams, String name, JSValue prop) {
|
||||
super.blend(view, layoutParams, name, prop);
|
||||
switch (name) {
|
||||
case "children":
|
||||
JSArray jsArray = prop.asArray();
|
||||
int i;
|
||||
List<ViewNode> tobeRemoved = new ArrayList<>();
|
||||
for (i = 0; i < jsArray.size(); i++) {
|
||||
JSValue jsValue = jsArray.get(i);
|
||||
if (!jsValue.isObject()) {
|
||||
continue;
|
||||
}
|
||||
JSObject childObj = jsValue.asObject();
|
||||
String type = childObj.getProperty("type").asString().value();
|
||||
String id = childObj.getProperty("id").asString().value();
|
||||
ViewNode child = mChildrenNode.get(id);
|
||||
if (child == null) {
|
||||
child = ViewNode.create(getDoricContext(), type);
|
||||
child.index = i;
|
||||
child.mParent = this;
|
||||
child.mId = id;
|
||||
mChildrenNode.put(id, child);
|
||||
} else {
|
||||
if (i != child.index) {
|
||||
mIndexInfo.remove(i);
|
||||
child.index = i;
|
||||
mView.removeView(child.getView());
|
||||
protected void blend(F view, String name, JSValue prop) {
|
||||
if ("children".equals(name)) {
|
||||
JSArray ids = prop.asArray();
|
||||
mChildViewIds.clear();
|
||||
for (int i = 0; i < ids.size(); i++) {
|
||||
mChildViewIds.add(ids.get(i).asString().value());
|
||||
}
|
||||
} else {
|
||||
super.blend(view, name, prop);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blend(JSObject jsObject) {
|
||||
super.blend(jsObject);
|
||||
configChildNode();
|
||||
}
|
||||
|
||||
private void configChildNode() {
|
||||
for (int idx = 0; idx < mChildViewIds.size(); idx++) {
|
||||
String id = mChildViewIds.get(idx);
|
||||
JSObject model = getSubModel(id);
|
||||
String type = model.getProperty("type").asString().value();
|
||||
if (idx < mChildNodes.size()) {
|
||||
ViewNode oldNode = mChildNodes.get(idx);
|
||||
if (id.equals(oldNode.getId())) {
|
||||
//The same,skip
|
||||
} else {
|
||||
if (mReusable) {
|
||||
if (oldNode.getType().equals(type)) {
|
||||
//Same type,can be reused
|
||||
oldNode.setId(id);
|
||||
oldNode.blend(model.getProperty("props").asObject());
|
||||
} else {
|
||||
//Replace this view
|
||||
mChildNodes.remove(idx);
|
||||
mView.removeView(oldNode.getDoricLayer());
|
||||
ViewNode newNode = ViewNode.create(getDoricContext(), type);
|
||||
newNode.setId(id);
|
||||
if (newNode instanceof GroupNode) {
|
||||
((GroupNode) newNode).mReusable = this.mReusable;
|
||||
}
|
||||
newNode.init(this);
|
||||
newNode.blend(model.getProperty("props").asObject());
|
||||
mChildNodes.add(idx, newNode);
|
||||
mView.addView(newNode.getDoricLayer(), idx, newNode.getLayoutParams());
|
||||
}
|
||||
tobeRemoved.remove(child);
|
||||
}
|
||||
} else {
|
||||
//Find in remain nodes
|
||||
int position = -1;
|
||||
for (int start = idx + 1; start < mChildNodes.size(); start++) {
|
||||
ViewNode node = mChildNodes.get(start);
|
||||
if (id.equals(node.getId())) {
|
||||
//Found
|
||||
position = start;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (position >= 0) {
|
||||
//Found swap idx,position
|
||||
ViewNode reused = mChildNodes.remove(position);
|
||||
ViewNode abandoned = mChildNodes.remove(idx);
|
||||
mChildNodes.set(idx, reused);
|
||||
mChildNodes.set(position, abandoned);
|
||||
//View swap index
|
||||
mView.removeView(reused.getDoricLayer());
|
||||
mView.addView(reused.getDoricLayer(), idx);
|
||||
mView.removeView(abandoned.getDoricLayer());
|
||||
mView.addView(abandoned.getDoricLayer(), position);
|
||||
} else {
|
||||
//Not found,insert
|
||||
ViewNode newNode = ViewNode.create(getDoricContext(), type);
|
||||
newNode.setId(id);
|
||||
newNode.init(this);
|
||||
newNode.blend(model.getProperty("props").asObject());
|
||||
|
||||
ViewNode node = mIndexInfo.get(i);
|
||||
|
||||
if (node != null && node != child) {
|
||||
mView.removeViewAt(i);
|
||||
mIndexInfo.remove(i);
|
||||
tobeRemoved.add(node);
|
||||
}
|
||||
|
||||
ViewGroup.LayoutParams params = child.getLayoutParams();
|
||||
if (params == null) {
|
||||
params = generateDefaultLayoutParams();
|
||||
}
|
||||
child.blend(childObj.getProperty("props").asObject(), params);
|
||||
if (mIndexInfo.get(i) == null) {
|
||||
mView.addView(child.getView(), i, child.getLayoutParams());
|
||||
mIndexInfo.put(i, child);
|
||||
mChildNodes.add(idx, newNode);
|
||||
mView.addView(newNode.getDoricLayer(), idx, newNode.getLayoutParams());
|
||||
}
|
||||
}
|
||||
}
|
||||
int count = mView.getChildCount();
|
||||
while (i < count) {
|
||||
ViewNode node = mIndexInfo.get(i);
|
||||
if (node != null) {
|
||||
mChildrenNode.remove(node.getId());
|
||||
mIndexInfo.remove(i);
|
||||
tobeRemoved.remove(node);
|
||||
mView.removeView(node.getView());
|
||||
}
|
||||
i++;
|
||||
} else {
|
||||
//Insert
|
||||
ViewNode newNode = ViewNode.create(getDoricContext(), type);
|
||||
newNode.setId(id);
|
||||
if (newNode instanceof GroupNode) {
|
||||
((GroupNode) newNode).mReusable = this.mReusable;
|
||||
}
|
||||
|
||||
for (ViewNode node : tobeRemoved) {
|
||||
mChildrenNode.remove(node.getId());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
super.blend(view, layoutParams, name, prop);
|
||||
break;
|
||||
newNode.init(this);
|
||||
newNode.blend(model.getProperty("props").asObject());
|
||||
mChildNodes.add(newNode);
|
||||
mView.addView(newNode.getDoricLayer(), idx, newNode.getLayoutParams());
|
||||
}
|
||||
}
|
||||
int size = mChildNodes.size();
|
||||
for (int idx = mChildViewIds.size(); idx < size; idx++) {
|
||||
ViewNode viewNode = mChildNodes.remove(mChildViewIds.size());
|
||||
mView.removeView(viewNode.getDoricLayer());
|
||||
}
|
||||
}
|
||||
|
||||
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
|
||||
return new ViewGroup.LayoutParams(0, 0);
|
||||
}
|
||||
|
||||
protected void blendChild(ViewNode viewNode, JSObject jsObject) {
|
||||
|
||||
|
||||
JSValue jsValue = jsObject.getProperty("margin");
|
||||
JSValue widthSpec = jsObject.getProperty("widthSpec");
|
||||
JSValue heightSpec = jsObject.getProperty("widthSpec");
|
||||
|
||||
ViewGroup.LayoutParams layoutParams = viewNode.getLayoutParams();
|
||||
if (widthSpec.isNumber()) {
|
||||
switch (widthSpec.asNumber().toInt()) {
|
||||
case 1:
|
||||
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
break;
|
||||
case 2:
|
||||
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
if (heightSpec.isNumber()) {
|
||||
switch (heightSpec.asNumber().toInt()) {
|
||||
case 1:
|
||||
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
break;
|
||||
case 2:
|
||||
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (jsValue.isObject() && layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
||||
JSValue topVal = jsValue.asObject().getProperty("top");
|
||||
if (topVal.isNumber()) {
|
||||
((ViewGroup.MarginLayoutParams) layoutParams).topMargin = DoricUtils.dp2px(topVal.asNumber().toFloat());
|
||||
}
|
||||
JSValue leftVal = jsValue.asObject().getProperty("left");
|
||||
if (leftVal.isNumber()) {
|
||||
((ViewGroup.MarginLayoutParams) layoutParams).leftMargin = DoricUtils.dp2px(leftVal.asNumber().toFloat());
|
||||
}
|
||||
JSValue rightVal = jsValue.asObject().getProperty("right");
|
||||
if (rightVal.isNumber()) {
|
||||
((ViewGroup.MarginLayoutParams) layoutParams).rightMargin = DoricUtils.dp2px(rightVal.asNumber().toFloat());
|
||||
}
|
||||
JSValue bottomVal = jsValue.asObject().getProperty("bottom");
|
||||
if (bottomVal.isNumber()) {
|
||||
((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = DoricUtils.dp2px(bottomVal.asNumber().toFloat());
|
||||
@Override
|
||||
protected void blendSubNode(JSObject subProp) {
|
||||
String subNodeId = subProp.getProperty("id").asString().value();
|
||||
for (ViewNode node : mChildNodes) {
|
||||
if (subNodeId.equals(node.getId())) {
|
||||
node.blend(subProp.getProperty("props").asObject());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,8 +34,8 @@ public class HLayoutNode extends LinearNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LinearLayout build(JSObject jsObject) {
|
||||
LinearLayout linearLayout = super.build(jsObject);
|
||||
protected LinearLayout build() {
|
||||
LinearLayout linearLayout = super.build();
|
||||
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
return linearLayout;
|
||||
}
|
||||
|
@ -46,12 +46,12 @@ public class ImageNode extends ViewNode<ImageView> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImageView build(JSObject jsObject) {
|
||||
protected ImageView build() {
|
||||
return new ImageView(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blend(ImageView view, ViewGroup.LayoutParams layoutParams, String name, JSValue prop) {
|
||||
protected void blend(ImageView view, String name, JSValue prop) {
|
||||
if ("imageUrl".equals(name)) {
|
||||
Glide.with(getContext()).load(prop.asString().value())
|
||||
.listener(new RequestListener<Drawable>() {
|
||||
@ -67,7 +67,7 @@ public class ImageNode extends ViewNode<ImageView> {
|
||||
})
|
||||
.into(view);
|
||||
} else {
|
||||
super.blend(view, layoutParams, name, prop);
|
||||
super.blend(view, name, prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +36,8 @@ public class LinearNode extends GroupNode<LinearLayout> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blendChild(ViewNode viewNode, JSObject layoutConfig) {
|
||||
super.blendChild(viewNode, layoutConfig);
|
||||
protected void blendSubLayoutConfig(ViewNode viewNode, JSObject layoutConfig) {
|
||||
super.blendSubLayoutConfig(viewNode, layoutConfig);
|
||||
JSValue jsValue = layoutConfig.getProperty("alignment");
|
||||
if (jsValue.isNumber()) {
|
||||
((LinearLayout.LayoutParams) viewNode.getLayoutParams()).gravity = jsValue.asNumber().toInt();
|
||||
@ -54,12 +54,12 @@ public class LinearNode extends GroupNode<LinearLayout> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LinearLayout build(JSObject jsObject) {
|
||||
protected LinearLayout build() {
|
||||
return new LinearLayout(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blend(LinearLayout view, ViewGroup.LayoutParams params, String name, JSValue prop) {
|
||||
protected void blend(LinearLayout view, String name, JSValue prop) {
|
||||
switch (name) {
|
||||
case "space":
|
||||
ShapeDrawable shapeDrawable;
|
||||
@ -82,7 +82,7 @@ public class LinearNode extends GroupNode<LinearLayout> {
|
||||
view.setGravity(prop.asNumber().toInt());
|
||||
break;
|
||||
default:
|
||||
super.blend(view, params, name, prop);
|
||||
super.blend(view, name, prop);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -36,12 +36,17 @@ public class RootNode extends StackNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
public View getDoricLayer() {
|
||||
return mView;
|
||||
}
|
||||
|
||||
public void setRootView(FrameLayout rootView) {
|
||||
this.mView = rootView;
|
||||
mLayoutParams = rootView.getLayoutParams();
|
||||
if (mLayoutParams == null) {
|
||||
mLayoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
rootView.setLayoutParams(mLayoutParams);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -50,6 +55,6 @@ public class RootNode extends StackNode {
|
||||
}
|
||||
|
||||
public void render(JSObject props) {
|
||||
blend(props, getLayoutParams());
|
||||
blend(props);
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +36,8 @@ public class StackNode extends GroupNode<FrameLayout> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blendChild(ViewNode viewNode, JSObject jsObject) {
|
||||
super.blendChild(viewNode, jsObject);
|
||||
protected void blendSubLayoutConfig(ViewNode viewNode, JSObject jsObject) {
|
||||
super.blendSubLayoutConfig(viewNode, jsObject);
|
||||
JSValue jsValue = jsObject.getProperty("alignment");
|
||||
if (jsValue.isNumber()) {
|
||||
((FrameLayout.LayoutParams) viewNode.getLayoutParams()).gravity = jsValue.asNumber().toInt();
|
||||
@ -45,18 +45,18 @@ public class StackNode extends GroupNode<FrameLayout> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FrameLayout build(JSObject jsObject) {
|
||||
protected FrameLayout build() {
|
||||
return new FrameLayout(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blend(FrameLayout view, ViewGroup.LayoutParams layoutParams, String name, JSValue prop) {
|
||||
protected void blend(FrameLayout view, String name, JSValue prop) {
|
||||
switch (name) {
|
||||
case "gravity":
|
||||
view.setForegroundGravity(prop.asNumber().toInt());
|
||||
break;
|
||||
default:
|
||||
super.blend(view, layoutParams, name, prop);
|
||||
super.blend(view, name, prop);
|
||||
}
|
||||
}
|
||||
|
||||
|
148
Android/doric/src/main/java/pub/doric/shader/SuperNode.java
Normal file
148
Android/doric/src/main/java/pub/doric/shader/SuperNode.java
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pub.doric.shader;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.github.pengfeizhou.jscore.JSArray;
|
||||
import com.github.pengfeizhou.jscore.JSObject;
|
||||
import com.github.pengfeizhou.jscore.JSValue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import pub.doric.DoricContext;
|
||||
import pub.doric.utils.DoricUtils;
|
||||
|
||||
/**
|
||||
* @Description: pub.doric.shader
|
||||
* @Author: pengfei.zhou
|
||||
* @CreateDate: 2019-11-13
|
||||
*/
|
||||
public abstract class SuperNode<V extends View> extends ViewNode<V> {
|
||||
private Map<String, JSObject> subNodes = new HashMap<>();
|
||||
|
||||
public SuperNode(DoricContext doricContext) {
|
||||
super(doricContext);
|
||||
}
|
||||
|
||||
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
|
||||
return new ViewGroup.LayoutParams(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blend(V view, String name, JSValue prop) {
|
||||
if (name.equals("subviews")) {
|
||||
if (prop.isArray()) {
|
||||
JSArray subviews = prop.asArray();
|
||||
for (int i = 0; i < subviews.size(); i++) {
|
||||
JSObject subNode = subviews.get(i).asObject();
|
||||
mixinSubNode(subNode);
|
||||
blendSubNode(subNode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
super.blend(view, name, prop);
|
||||
}
|
||||
}
|
||||
|
||||
private void mixinSubNode(JSObject subNode) {
|
||||
String id = subNode.getProperty("id").asString().value();
|
||||
JSObject targetNode = subNodes.get(id);
|
||||
if (targetNode == null) {
|
||||
subNodes.put(id, subNode);
|
||||
} else {
|
||||
mixin(subNode, targetNode);
|
||||
}
|
||||
}
|
||||
|
||||
public JSObject getSubModel(String id) {
|
||||
return subNodes.get(id);
|
||||
}
|
||||
|
||||
public void setSubModel(String id, JSObject model) {
|
||||
subNodes.put(id, model);
|
||||
}
|
||||
|
||||
public void clearSubModel() {
|
||||
subNodes.clear();
|
||||
}
|
||||
|
||||
protected abstract void blendSubNode(JSObject subProperties);
|
||||
|
||||
protected void blendSubLayoutConfig(ViewNode viewNode, JSObject jsObject) {
|
||||
JSValue margin = jsObject.getProperty("margin");
|
||||
JSValue widthSpec = jsObject.getProperty("widthSpec");
|
||||
JSValue heightSpec = jsObject.getProperty("heightSpec");
|
||||
ViewGroup.LayoutParams layoutParams = viewNode.getLayoutParams();
|
||||
if (widthSpec.isNumber()) {
|
||||
switch (widthSpec.asNumber().toInt()) {
|
||||
case 1:
|
||||
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
break;
|
||||
case 2:
|
||||
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (heightSpec.isNumber()) {
|
||||
switch (heightSpec.asNumber().toInt()) {
|
||||
case 1:
|
||||
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
break;
|
||||
case 2:
|
||||
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (margin.isObject() && layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
||||
JSValue topVal = margin.asObject().getProperty("top");
|
||||
if (topVal.isNumber()) {
|
||||
((ViewGroup.MarginLayoutParams) layoutParams).topMargin = DoricUtils.dp2px(topVal.asNumber().toFloat());
|
||||
}
|
||||
JSValue leftVal = margin.asObject().getProperty("left");
|
||||
if (leftVal.isNumber()) {
|
||||
((ViewGroup.MarginLayoutParams) layoutParams).leftMargin = DoricUtils.dp2px(leftVal.asNumber().toFloat());
|
||||
}
|
||||
JSValue rightVal = margin.asObject().getProperty("right");
|
||||
if (rightVal.isNumber()) {
|
||||
((ViewGroup.MarginLayoutParams) layoutParams).rightMargin = DoricUtils.dp2px(rightVal.asNumber().toFloat());
|
||||
}
|
||||
JSValue bottomVal = margin.asObject().getProperty("bottom");
|
||||
if (bottomVal.isNumber()) {
|
||||
((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = DoricUtils.dp2px(bottomVal.asNumber().toFloat());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void mixin(JSObject src, JSObject target) {
|
||||
JSObject srcProps = src.getProperty("props").asObject();
|
||||
JSObject targetProps = target.getProperty("props").asObject();
|
||||
for (String key : srcProps.propertySet()) {
|
||||
JSValue jsValue = srcProps.getProperty(key);
|
||||
if ("subviews".equals(key) && jsValue.isArray()) {
|
||||
continue;
|
||||
}
|
||||
targetProps.asObject().setProperty(key, jsValue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -16,13 +16,11 @@
|
||||
package pub.doric.shader;
|
||||
|
||||
import android.util.TypedValue;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import pub.doric.DoricContext;
|
||||
import pub.doric.extension.bridge.DoricPlugin;
|
||||
|
||||
import com.github.pengfeizhou.jscore.JSObject;
|
||||
import com.github.pengfeizhou.jscore.JSValue;
|
||||
|
||||
/**
|
||||
@ -37,12 +35,12 @@ public class TextNode extends ViewNode<TextView> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TextView build(JSObject jsObject) {
|
||||
protected TextView build() {
|
||||
return new TextView(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blend(TextView view, ViewGroup.LayoutParams params, String name, JSValue prop) {
|
||||
protected void blend(TextView view, String name, JSValue prop) {
|
||||
switch (name) {
|
||||
case "text":
|
||||
view.setText(prop.asString().toString());
|
||||
@ -57,7 +55,7 @@ public class TextNode extends ViewNode<TextView> {
|
||||
view.setGravity(prop.asNumber().toInt());
|
||||
break;
|
||||
default:
|
||||
super.blend(view, params, name, prop);
|
||||
super.blend(view, name, prop);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,6 @@ import android.widget.LinearLayout;
|
||||
import pub.doric.DoricContext;
|
||||
import pub.doric.extension.bridge.DoricPlugin;
|
||||
|
||||
import com.github.pengfeizhou.jscore.JSObject;
|
||||
|
||||
/**
|
||||
* @Description: com.github.penfeizhou.doric.shader
|
||||
* @Author: pengfei.zhou
|
||||
@ -34,8 +32,8 @@ public class VLayoutNode extends LinearNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LinearLayout build(JSObject jsObject) {
|
||||
LinearLayout linearLayout = super.build(jsObject);
|
||||
protected LinearLayout build() {
|
||||
LinearLayout linearLayout = super.build();
|
||||
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
return linearLayout;
|
||||
}
|
||||
|
@ -22,11 +22,13 @@ import android.widget.FrameLayout;
|
||||
|
||||
import pub.doric.DoricContext;
|
||||
import pub.doric.DoricRegistry;
|
||||
import pub.doric.async.AsyncResult;
|
||||
import pub.doric.utils.DoricContextHolder;
|
||||
import pub.doric.utils.DoricConstant;
|
||||
import pub.doric.utils.DoricMetaInfo;
|
||||
import pub.doric.utils.DoricUtils;
|
||||
|
||||
import com.github.pengfeizhou.jscore.JSDecoder;
|
||||
import com.github.pengfeizhou.jscore.JSObject;
|
||||
import com.github.pengfeizhou.jscore.JSValue;
|
||||
|
||||
@ -39,10 +41,10 @@ import java.util.LinkedList;
|
||||
*/
|
||||
public abstract class ViewNode<T extends View> extends DoricContextHolder {
|
||||
protected T mView;
|
||||
int index;
|
||||
GroupNode mParent;
|
||||
SuperNode mSuperNode;
|
||||
String mId;
|
||||
private ViewGroup.LayoutParams mLayoutParams;
|
||||
protected ViewGroup.LayoutParams mLayoutParams;
|
||||
private String mType;
|
||||
|
||||
public ViewNode(DoricContext doricContext) {
|
||||
super(doricContext);
|
||||
@ -50,7 +52,25 @@ public abstract class ViewNode<T extends View> extends DoricContextHolder {
|
||||
|
||||
private DoricLayer doricLayer;
|
||||
|
||||
public View getView() {
|
||||
public void init(SuperNode superNode) {
|
||||
this.mSuperNode = superNode;
|
||||
this.mLayoutParams = superNode.generateDefaultLayoutParams();
|
||||
this.doricLayer = new DoricLayer(getContext());
|
||||
this.doricLayer.setLayoutParams(mLayoutParams);
|
||||
this.mView = build();
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(mLayoutParams.width, mLayoutParams.height);
|
||||
doricLayer.addView(mView, params);
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.mId = id;
|
||||
}
|
||||
|
||||
public String getType(){
|
||||
return mType;
|
||||
}
|
||||
|
||||
public View getDoricLayer() {
|
||||
return doricLayer;
|
||||
}
|
||||
|
||||
@ -58,53 +78,46 @@ public abstract class ViewNode<T extends View> extends DoricContextHolder {
|
||||
return getDoricContext().getContext();
|
||||
}
|
||||
|
||||
protected abstract T build(JSObject jsObject);
|
||||
protected abstract T build();
|
||||
|
||||
void blend(JSObject jsObject, ViewGroup.LayoutParams layoutParams) {
|
||||
mLayoutParams = layoutParams;
|
||||
if (mView == null) {
|
||||
mView = build(jsObject);
|
||||
}
|
||||
if (getView() == null) {
|
||||
doricLayer = new DoricLayer(getContext());
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(layoutParams.width, layoutParams.height);
|
||||
doricLayer.addView(mView, params);
|
||||
}
|
||||
for (String prop : jsObject.propertySet()) {
|
||||
blend(mView, layoutParams, prop, jsObject.getProperty(prop));
|
||||
public void blend(JSObject jsObject) {
|
||||
if (jsObject != null) {
|
||||
for (String prop : jsObject.propertySet()) {
|
||||
blend(mView, prop, jsObject.getProperty(prop));
|
||||
}
|
||||
}
|
||||
ViewGroup.LayoutParams params = mView.getLayoutParams();
|
||||
if (params != null) {
|
||||
params.width = layoutParams.width;
|
||||
params.height = layoutParams.height;
|
||||
params.width = mLayoutParams.width;
|
||||
params.height = mLayoutParams.height;
|
||||
} else {
|
||||
params = layoutParams;
|
||||
params = mLayoutParams;
|
||||
}
|
||||
mView.setLayoutParams(params);
|
||||
}
|
||||
|
||||
protected void blend(T view, ViewGroup.LayoutParams layoutParams, String name, JSValue prop) {
|
||||
protected void blend(T view, String name, JSValue prop) {
|
||||
switch (name) {
|
||||
case "width":
|
||||
if (layoutParams.width >= 0) {
|
||||
layoutParams.width = DoricUtils.dp2px(prop.asNumber().toFloat());
|
||||
if (mLayoutParams.width >= 0) {
|
||||
mLayoutParams.width = DoricUtils.dp2px(prop.asNumber().toFloat());
|
||||
}
|
||||
break;
|
||||
case "height":
|
||||
if (layoutParams.height >= 0) {
|
||||
layoutParams.height = DoricUtils.dp2px(prop.asNumber().toFloat());
|
||||
if (mLayoutParams.height >= 0) {
|
||||
mLayoutParams.height = DoricUtils.dp2px(prop.asNumber().toFloat());
|
||||
}
|
||||
break;
|
||||
case "x":
|
||||
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
||||
if (mLayoutParams instanceof ViewGroup.MarginLayoutParams) {
|
||||
float x = prop.asNumber().toFloat();
|
||||
((ViewGroup.MarginLayoutParams) layoutParams).leftMargin = DoricUtils.dp2px(x);
|
||||
((ViewGroup.MarginLayoutParams) mLayoutParams).leftMargin = DoricUtils.dp2px(x);
|
||||
}
|
||||
break;
|
||||
case "y":
|
||||
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
||||
if (mLayoutParams instanceof ViewGroup.MarginLayoutParams) {
|
||||
float y = prop.asNumber().toFloat();
|
||||
((ViewGroup.MarginLayoutParams) layoutParams).topMargin = DoricUtils.dp2px(y);
|
||||
((ViewGroup.MarginLayoutParams) mLayoutParams).topMargin = DoricUtils.dp2px(y);
|
||||
}
|
||||
break;
|
||||
case "bgColor":
|
||||
@ -120,8 +133,8 @@ public abstract class ViewNode<T extends View> extends DoricContextHolder {
|
||||
});
|
||||
break;
|
||||
case "layoutConfig":
|
||||
if (prop.isObject() && mParent != null) {
|
||||
mParent.blendChild(this, prop.asObject());
|
||||
if (prop.isObject() && mSuperNode != null) {
|
||||
mSuperNode.blendSubLayoutConfig(this, prop.asObject());
|
||||
}
|
||||
break;
|
||||
case "border":
|
||||
@ -172,26 +185,28 @@ public abstract class ViewNode<T extends View> extends DoricContextHolder {
|
||||
ViewNode viewNode = this;
|
||||
do {
|
||||
ids.push(viewNode.mId);
|
||||
viewNode = viewNode.mParent;
|
||||
viewNode = viewNode.mSuperNode;
|
||||
} while (viewNode != null && !(viewNode instanceof RootNode));
|
||||
|
||||
return ids.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public void callJSResponse(String funcId, Object... args) {
|
||||
public AsyncResult<JSDecoder> callJSResponse(String funcId, Object... args) {
|
||||
final Object[] nArgs = new Object[args.length + 2];
|
||||
nArgs[0] = getIdList();
|
||||
nArgs[1] = funcId;
|
||||
if (args.length > 0) {
|
||||
System.arraycopy(args, 0, nArgs, 2, args.length);
|
||||
}
|
||||
getDoricContext().callEntity(DoricConstant.DORIC_ENTITY_RESPONSE, nArgs);
|
||||
return getDoricContext().callEntity(DoricConstant.DORIC_ENTITY_RESPONSE, nArgs);
|
||||
}
|
||||
|
||||
public static ViewNode create(DoricContext doricContext, String type) {
|
||||
DoricRegistry registry = doricContext.getDriver().getRegistry();
|
||||
DoricMetaInfo<ViewNode> clz = registry.acquireViewNodeInfo(type);
|
||||
return clz.createInstance(doricContext);
|
||||
ViewNode ret = clz.createInstance(doricContext);
|
||||
ret.mType = type;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public ViewGroup.LayoutParams getLayoutParams() {
|
||||
|
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pub.doric.shader.list;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.github.pengfeizhou.jscore.JSArray;
|
||||
import com.github.pengfeizhou.jscore.JSDecoder;
|
||||
import com.github.pengfeizhou.jscore.JSNull;
|
||||
import com.github.pengfeizhou.jscore.JSObject;
|
||||
import com.github.pengfeizhou.jscore.JSValue;
|
||||
|
||||
import pub.doric.async.AsyncResult;
|
||||
import pub.doric.shader.ViewNode;
|
||||
|
||||
/**
|
||||
* @Description: com.github.penfeizhou.doric.widget
|
||||
* @Author: pengfei.zhou
|
||||
* @CreateDate: 2019-11-12
|
||||
*/
|
||||
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.DoricViewHolder> {
|
||||
|
||||
private final ListNode listNode;
|
||||
String renderItemFuncId;
|
||||
int itemCount = 0;
|
||||
int batchCount = 15;
|
||||
SparseArray<String> itemValues = new SparseArray<>();
|
||||
|
||||
ListAdapter(ListNode listNode) {
|
||||
this.listNode = listNode;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DoricViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
ListItemNode node = (ListItemNode) ViewNode.create(listNode.getDoricContext(), "ListItem");
|
||||
node.init(listNode);
|
||||
return new DoricViewHolder(node, node.getDoricLayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull DoricViewHolder holder, int position) {
|
||||
JSValue jsValue = getItemModel(position);
|
||||
if (jsValue.isObject()) {
|
||||
JSObject jsObject = jsValue.asObject();
|
||||
holder.listItemNode.setId(jsObject.getProperty("id").asString().value());
|
||||
holder.listItemNode.blend(jsObject.getProperty("props").asObject());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return itemCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
JSValue value = getItemModel(position);
|
||||
if (value.isObject()) {
|
||||
if (value.asObject().getProperty("identifier").isString()) {
|
||||
return value.asObject().getProperty("identifier").asString().value().hashCode();
|
||||
}
|
||||
}
|
||||
return super.getItemViewType(position);
|
||||
}
|
||||
|
||||
private JSValue getItemModel(final int position) {
|
||||
String id = itemValues.get(position);
|
||||
if (TextUtils.isEmpty(id)) {
|
||||
AsyncResult<JSDecoder> asyncResult = listNode.callJSResponse(
|
||||
"renderBunchedItems",
|
||||
position,
|
||||
batchCount);
|
||||
try {
|
||||
JSDecoder jsDecoder = asyncResult.synchronous().get();
|
||||
JSValue result = jsDecoder.decode();
|
||||
if (result.isArray()) {
|
||||
JSArray jsArray = result.asArray();
|
||||
for (int i = 0; i < jsArray.size(); i++) {
|
||||
JSObject itemModel = jsArray.get(i).asObject();
|
||||
String itemId = itemModel.getProperty("id").asString().value();
|
||||
itemValues.put(i + position, itemId);
|
||||
listNode.setSubModel(itemId, itemModel);
|
||||
}
|
||||
return listNode.getSubModel(itemValues.get(position));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new JSNull();
|
||||
} else {
|
||||
JSObject childModel = listNode.getSubModel(id);
|
||||
if (childModel == null) {
|
||||
return new JSNull();
|
||||
} else {
|
||||
return childModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void blendSubNode(JSObject subProperties) {
|
||||
for (int i = 0; i < itemValues.size(); i++) {
|
||||
if (subProperties.getProperty("id").asString().value().equals(itemValues.valueAt(i))) {
|
||||
notifyItemChanged(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class DoricViewHolder extends RecyclerView.ViewHolder {
|
||||
ListItemNode listItemNode;
|
||||
|
||||
DoricViewHolder(ListItemNode node, @NonNull View itemView) {
|
||||
super(itemView);
|
||||
listItemNode = node;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pub.doric.shader.list;
|
||||
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.github.pengfeizhou.jscore.JSValue;
|
||||
|
||||
import pub.doric.DoricContext;
|
||||
import pub.doric.extension.bridge.DoricPlugin;
|
||||
import pub.doric.shader.StackNode;
|
||||
|
||||
/**
|
||||
* @Description: com.github.penfeizhou.doric.widget
|
||||
* @Author: pengfei.zhou
|
||||
* @CreateDate: 2019-11-12
|
||||
*/
|
||||
@DoricPlugin(name = "ListItem")
|
||||
public class ListItemNode extends StackNode {
|
||||
public String identifier = "";
|
||||
|
||||
public ListItemNode(DoricContext doricContext) {
|
||||
super(doricContext);
|
||||
this.mReusable = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blend(FrameLayout view, String name, JSValue prop) {
|
||||
if ("identifier".equals(name)) {
|
||||
this.identifier = prop.asString().value();
|
||||
} else {
|
||||
super.blend(view, name, prop);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pub.doric.shader.list;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.github.pengfeizhou.jscore.JSObject;
|
||||
import com.github.pengfeizhou.jscore.JSValue;
|
||||
|
||||
import pub.doric.DoricContext;
|
||||
import pub.doric.extension.bridge.DoricPlugin;
|
||||
import pub.doric.shader.SuperNode;
|
||||
import pub.doric.shader.ViewNode;
|
||||
|
||||
/**
|
||||
* @Description: com.github.penfeizhou.doric.widget
|
||||
* @Author: pengfei.zhou
|
||||
* @CreateDate: 2019-11-12
|
||||
*/
|
||||
@DoricPlugin(name = "List")
|
||||
public class ListNode extends SuperNode<RecyclerView> {
|
||||
private final ListAdapter listAdapter;
|
||||
|
||||
public ListNode(DoricContext doricContext) {
|
||||
super(doricContext);
|
||||
this.listAdapter = new ListAdapter(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blendSubNode(JSObject subProperties) {
|
||||
listAdapter.blendSubNode(subProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView build() {
|
||||
RecyclerView recyclerView = new RecyclerView(getContext());
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
recyclerView.setAdapter(this.listAdapter);
|
||||
return recyclerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blend(JSObject jsObject) {
|
||||
super.blend(jsObject);
|
||||
if (mView != null) {
|
||||
mView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blend(RecyclerView view, String name, JSValue prop) {
|
||||
switch (name) {
|
||||
case "itemCount":
|
||||
this.listAdapter.itemCount = prop.asNumber().toInt();
|
||||
break;
|
||||
case "renderItem":
|
||||
this.listAdapter.renderItemFuncId = prop.asString().value();
|
||||
// If reset renderItem,should reset native cache.
|
||||
this.listAdapter.itemValues.clear();
|
||||
clearSubModel();
|
||||
break;
|
||||
case "batchCount":
|
||||
this.listAdapter.batchCount = 15;
|
||||
break;
|
||||
default:
|
||||
super.blend(view, name, prop);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blendSubLayoutConfig(ViewNode viewNode, JSObject jsObject) {
|
||||
super.blendSubLayoutConfig(viewNode, jsObject);
|
||||
}
|
||||
}
|
@ -14,4 +14,9 @@ org.gradle.jvmargs=-Xmx1536m
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# To re-enable the build cache, either delete the following
|
||||
# line or set the property to 'true'.
|
||||
android.enableBuildCache=false
|
||||
|
||||
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
export default [
|
||||
'src/Counter',
|
||||
'src/Snake',
|
||||
'src/ListDemo',
|
||||
]
|
@ -13,7 +13,9 @@ class CounterView extends ViewHolder<CountModel> {
|
||||
text({
|
||||
textSize: 40,
|
||||
layoutConfig: {
|
||||
alignment: new Gravity().center()
|
||||
alignment: new Gravity().center(),
|
||||
widthSpec: LayoutSpec.WRAP_CONTENT,
|
||||
heightSpec: LayoutSpec.WRAP_CONTENT,
|
||||
},
|
||||
}).also(it => { this.number = it }),
|
||||
text({
|
||||
@ -25,7 +27,9 @@ class CounterView extends ViewHolder<CountModel> {
|
||||
},
|
||||
corners: 5,
|
||||
layoutConfig: {
|
||||
alignment: new Gravity().center()
|
||||
alignment: new Gravity().center(),
|
||||
widthSpec: LayoutSpec.WRAP_CONTENT,
|
||||
heightSpec: LayoutSpec.WRAP_CONTENT,
|
||||
},
|
||||
shadow: {
|
||||
color: Color.parse("#00ff00"),
|
||||
|
71
demo/src/ListDemo.ts
Normal file
71
demo/src/ListDemo.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { Group, Panel, List, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout } from "doric";
|
||||
const colors = [
|
||||
"#f0932b",
|
||||
"#eb4d4b",
|
||||
"#6ab04c",
|
||||
"#e056fd",
|
||||
"#686de0",
|
||||
"#30336b",
|
||||
]
|
||||
@Entry
|
||||
class ListPanel extends Panel {
|
||||
build(rootView: Group): void {
|
||||
rootView.addChild(vlayout([
|
||||
text({
|
||||
text: "ListDemo",
|
||||
layoutConfig: {
|
||||
widthSpec: LayoutSpec.AT_MOST,
|
||||
heightSpec: LayoutSpec.EXACTLY,
|
||||
},
|
||||
textSize: 30,
|
||||
textColor: Color.parse("#535c68"),
|
||||
bgColor: Color.parse("#dff9fb"),
|
||||
textAlignment: gravity().center(),
|
||||
height: 50,
|
||||
}),
|
||||
list({
|
||||
itemCount: 1000,
|
||||
renderItem: (idx: number) => {
|
||||
return listItem(text({
|
||||
layoutConfig: {
|
||||
widthSpec: LayoutSpec.AT_MOST,
|
||||
heightSpec: LayoutSpec.WRAP_CONTENT,
|
||||
margin: {
|
||||
left: 10,
|
||||
right: 50,
|
||||
top: 50,
|
||||
bottom: 10,
|
||||
},
|
||||
},
|
||||
text: `Cell At Line ${idx}`,
|
||||
textAlignment: gravity().center(),
|
||||
textColor: Color.parse("#ffffff"),
|
||||
textSize: 20,
|
||||
})).also(it => {
|
||||
it.gravity = gravity().center()
|
||||
it.bgColor = Color.parse(colors[idx % colors.length])
|
||||
it.layoutConfig = {
|
||||
widthSpec: LayoutSpec.AT_MOST,
|
||||
heightSpec: LayoutSpec.EXACTLY,
|
||||
}
|
||||
it.height = 100
|
||||
it.onClick = () => {
|
||||
log(`Click item at ${idx}`)
|
||||
it.bgColor = Color.parse('#000000')
|
||||
log(`bgcolor is ${Color.parse('#000000').toModel()}`)
|
||||
}
|
||||
})
|
||||
},
|
||||
layoutConfig: {
|
||||
widthSpec: LayoutSpec.AT_MOST,
|
||||
heightSpec: LayoutSpec.AT_MOST,
|
||||
},
|
||||
}),
|
||||
]).also(it => {
|
||||
it.layoutConfig = {
|
||||
widthSpec: LayoutSpec.AT_MOST,
|
||||
heightSpec: LayoutSpec.AT_MOST,
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@
|
||||
E2334AFE22E9D2070098A085 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E2334AFD22E9D2070098A085 /* main.m */; };
|
||||
E2334B0822E9D2070098A085 /* ExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E2334B0722E9D2070098A085 /* ExampleTests.m */; };
|
||||
E2334B1322E9D2070098A085 /* ExampleUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = E2334B1222E9D2070098A085 /* ExampleUITests.m */; };
|
||||
E2BF7BF7237E8E9F001B0EDC /* ListDemo.js in Resources */ = {isa = PBXBuildFile; fileRef = E2BF7BF6237E8E9F001B0EDC /* ListDemo.js */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -67,6 +68,7 @@
|
||||
E2334B0E22E9D2070098A085 /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
E2334B1222E9D2070098A085 /* ExampleUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleUITests.m; sourceTree = "<group>"; };
|
||||
E2334B1422E9D2070098A085 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
E2BF7BF6237E8E9F001B0EDC /* ListDemo.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = ListDemo.js; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -123,6 +125,7 @@
|
||||
E21DC9D12302865E00660C5C /* src */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2BF7BF6237E8E9F001B0EDC /* ListDemo.js */,
|
||||
E21DC9D22302865E00660C5C /* Snake.js */,
|
||||
E21DC9D32302865E00660C5C /* Counter.js */,
|
||||
);
|
||||
@ -295,6 +298,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E2BF7BF7237E8E9F001B0EDC /* ListDemo.js in Resources */,
|
||||
E21DC9D42302870000660C5C /* Snake.js in Resources */,
|
||||
E21DC9D52302870000660C5C /* Counter.js in Resources */,
|
||||
E2334AFB22E9D2070098A085 /* LaunchScreen.storyboard in Resources */,
|
||||
|
@ -26,11 +26,11 @@ @implementation ViewController
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"Snake" ofType:@"js"];
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"ListDemo" ofType:@"js"];
|
||||
NSString *jsContent = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
|
||||
self.doricContext = [[DoricContext alloc] initWithScript:jsContent source:@"test.js"];
|
||||
[self.doricContext.rootNode setupRootView:[[DoricStackView new] also:^(DoricStackView *it) {
|
||||
it.layoutConfig = [[DoricStackConfig alloc] initWithWidth:DoricLayoutAtMost height:DoricLayoutAtMost];
|
||||
it.layoutConfig = [[DoricLayoutConfig alloc] initWithWidth:DoricLayoutAtMost height:DoricLayoutAtMost];
|
||||
[self.view addSubview:it];
|
||||
}]];
|
||||
[self.doricContext initContextWithWidth:self.view.width height:self.view.height];
|
||||
|
@ -25,7 +25,7 @@
|
||||
@implementation DoricContextHolder
|
||||
|
||||
- (instancetype)initWithContext:(DoricContext *)doricContext {
|
||||
if (self = [super init]) {
|
||||
if (self = [self init]) {
|
||||
_doricContext = doricContext;
|
||||
}
|
||||
return self;
|
||||
|
@ -28,6 +28,8 @@
|
||||
#import "DoricHLayoutNode.h"
|
||||
#import "DoricTextNode.h"
|
||||
#import "DoricImageNode.h"
|
||||
#import "DoricListNode.h"
|
||||
#import "DoricListItemNode.h"
|
||||
|
||||
@interface DoricRegistry ()
|
||||
|
||||
@ -58,6 +60,8 @@ - (void)innerRegister {
|
||||
[self registerViewNode:DoricHLayoutNode.class withName:@"HLayout"];
|
||||
[self registerViewNode:DoricTextNode.class withName:@"Text"];
|
||||
[self registerViewNode:DoricImageNode.class withName:@"Image"];
|
||||
[self registerViewNode:DoricListNode.class withName:@"List"];
|
||||
[self registerViewNode:DoricListItemNode.class withName:@"ListItem"];
|
||||
}
|
||||
|
||||
- (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name {
|
||||
|
@ -20,19 +20,12 @@
|
||||
// Created by pengfei.zhou on 2019/7/30.
|
||||
//
|
||||
|
||||
#import "DoricViewNode.h"
|
||||
#import "DoricSuperNode.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface DoricGroupNode <V:UIView *, P:DoricLayoutConfig *> : DoricViewNode<V>
|
||||
|
||||
@property(nonatomic, strong) NSMutableDictionary *children;
|
||||
@property(nonatomic, strong) NSMutableArray *indexedChildren;
|
||||
|
||||
|
||||
- (void)blendChild:(DoricViewNode *)child layoutConfig:(NSDictionary *)layoutConfig;
|
||||
|
||||
- (P)generateDefaultLayoutParams;
|
||||
@interface DoricGroupNode <V:UIView *> : DoricSuperNode<V>
|
||||
@property(nonatomic, assign) BOOL reusable;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@ -23,124 +23,140 @@
|
||||
#import <Doric/DoricExtensions.h>
|
||||
#import "DoricGroupNode.h"
|
||||
|
||||
@interface DoricGroupNode ()
|
||||
@property(nonatomic, copy) NSArray<DoricViewNode *> *childNodes;
|
||||
@property(nonatomic, copy) NSArray <NSString *> *childViewIds;
|
||||
@end
|
||||
|
||||
@implementation DoricGroupNode
|
||||
|
||||
- (instancetype)initWithContext:(DoricContext *)doricContext {
|
||||
if (self = [super initWithContext:doricContext]) {
|
||||
_children = [[NSMutableDictionary alloc] init];
|
||||
_indexedChildren = [[NSMutableArray alloc] init];
|
||||
_childNodes = @[];
|
||||
_childViewIds = @[];
|
||||
_reusable = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (UIView *)build:(NSDictionary *)props {
|
||||
- (UIView *)build {
|
||||
UIView *ret = [[UIView alloc] init];
|
||||
ret.clipsToBounds = YES;
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (void)blendView:(UIView *)view forPropName:(NSString *)name propValue:(id)prop {
|
||||
if ([name isEqualToString:@"children"]) {
|
||||
NSArray *array = prop;
|
||||
NSInteger i;
|
||||
NSMutableArray *tobeRemoved = [[NSMutableArray alloc] init];
|
||||
for (i = 0; i < array.count; i++) {
|
||||
NSDictionary *val = array[i];
|
||||
if (!val || (NSNull *) val == [NSNull null]) {
|
||||
continue;
|
||||
}
|
||||
NSString *type = val[@"type"];
|
||||
NSString *viewId = val[@"id"];
|
||||
DoricViewNode *node = self.children[viewId];
|
||||
if (node == nil) {
|
||||
node = [DoricViewNode create:self.doricContext withType:type];
|
||||
node.index = i;
|
||||
node.parent = self;
|
||||
node.viewId = viewId;
|
||||
self.children[viewId] = node;
|
||||
} else {
|
||||
if (i != node.index) {
|
||||
[self.indexedChildren removeObjectAtIndex:i];
|
||||
node.index = i;
|
||||
[node.view removeFromSuperview];
|
||||
}
|
||||
[tobeRemoved removeObject:node];
|
||||
}
|
||||
DoricViewNode *old = i >= self.indexedChildren.count ? nil : self.indexedChildren[i];
|
||||
if (old && old != node) {
|
||||
[old.view removeFromSuperview];
|
||||
self.indexedChildren[i] = [NSNull null];
|
||||
[tobeRemoved addObject:old];
|
||||
}
|
||||
|
||||
DoricLayoutConfig *params = node.layoutConfig;
|
||||
if (params == nil) {
|
||||
params = [self generateDefaultLayoutParams];
|
||||
node.layoutConfig = params;
|
||||
}
|
||||
[node blend:val[@"props"]];
|
||||
if (self.indexedChildren.count <= i) {
|
||||
[self.view addSubview:node.view];
|
||||
[self.indexedChildren addObject:node];
|
||||
} else if (self.indexedChildren[i] == [NSNull null]) {
|
||||
self.indexedChildren[i] = node;
|
||||
[self.view insertSubview:node.view atIndex:i];
|
||||
}
|
||||
}
|
||||
NSInteger start = i;
|
||||
while (start < self.indexedChildren.count) {
|
||||
DoricViewNode *node = self.indexedChildren[(NSUInteger) start];
|
||||
if (node) {
|
||||
[self.children removeObjectForKey:node.viewId];
|
||||
[node.view removeFromSuperview];
|
||||
[tobeRemoved removeObject:node];
|
||||
}
|
||||
start++;
|
||||
}
|
||||
if (i < self.indexedChildren.count) {
|
||||
[self.indexedChildren removeObjectsInRange:NSMakeRange((NSUInteger) i, self.indexedChildren.count - i)];
|
||||
}
|
||||
|
||||
for (DoricViewNode *node in tobeRemoved) {
|
||||
[self.children removeObjectForKey:node.viewId];
|
||||
}
|
||||
if ([@"children" isEqualToString:name]) {
|
||||
self.childViewIds = prop;
|
||||
} else {
|
||||
[super blendView:view forPropName:name propValue:prop];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)blend:(NSDictionary *)props {
|
||||
[super blend:props];
|
||||
[self configChildNodes];
|
||||
}
|
||||
|
||||
- (DoricLayoutConfig *)generateDefaultLayoutParams {
|
||||
DoricLayoutConfig *params = [[DoricLayoutConfig alloc] init];
|
||||
return params;
|
||||
}
|
||||
|
||||
- (void)blendChild:(DoricViewNode *)child layoutConfig:(NSDictionary *)layoutConfig {
|
||||
DoricLayoutConfig *params = child.layoutConfig;
|
||||
- (void)configChildNodes {
|
||||
NSMutableArray *childNodes = [self.childNodes mutableCopy];
|
||||
for (NSUInteger idx = 0; idx < self.childViewIds.count; idx++) {
|
||||
NSString *viewId = self.childViewIds[idx];
|
||||
NSDictionary *model = [self subModelOf:viewId];
|
||||
NSString *type = model[@"type"];
|
||||
if (idx < self.childNodes.count) {
|
||||
DoricViewNode *oldNode = childNodes[idx];
|
||||
if ([viewId isEqualToString:oldNode.viewId]) {
|
||||
///Same,skip
|
||||
} else {
|
||||
if (self.reusable) {
|
||||
if ([oldNode.type isEqualToString:type]) {
|
||||
///Same type,can be reused
|
||||
oldNode.viewId = viewId;
|
||||
[oldNode blend:model[@"props"]];
|
||||
} else {
|
||||
///Replace this view
|
||||
[childNodes removeObjectAtIndex:idx];
|
||||
[oldNode.view removeFromSuperview];
|
||||
DoricViewNode *viewNode = [DoricViewNode create:self.doricContext withType:type];
|
||||
if ([viewNode isKindOfClass:[DoricGroupNode class]]) {
|
||||
((DoricGroupNode *) viewNode).reusable = self.reusable;
|
||||
}
|
||||
viewNode.viewId = viewId;
|
||||
[viewNode initWithSuperNode:self];
|
||||
[viewNode blend:model[@"props"]];
|
||||
[childNodes insertObject:viewNode atIndex:idx];
|
||||
[self.view insertSubview:viewNode.view atIndex:idx];
|
||||
}
|
||||
} else {
|
||||
///Find in remain nodes
|
||||
NSInteger position = -1;
|
||||
for (NSUInteger start = idx + 1; start < childNodes.count; start++) {
|
||||
DoricViewNode *node = childNodes[start];
|
||||
if ([viewId isEqualToString:node.viewId]) {
|
||||
position = start;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (position >= 0) {
|
||||
///Found ,swap idx,position
|
||||
DoricViewNode *reused = childNodes[(NSUInteger) position];
|
||||
[childNodes removeObjectAtIndex:(NSUInteger) position];
|
||||
[childNodes removeObjectAtIndex:idx];
|
||||
[childNodes insertObject:reused atIndex:idx];
|
||||
[childNodes insertObject:oldNode atIndex:(NSUInteger) position];
|
||||
|
||||
[layoutConfig[@"widthSpec"] also:^(NSNumber *it) {
|
||||
if (it) {
|
||||
params.widthSpec = (DoricLayoutSpec) [it integerValue];
|
||||
}
|
||||
}];
|
||||
|
||||
[layoutConfig[@"heightSpec"] also:^(NSNumber *it) {
|
||||
if (it) {
|
||||
params.heightSpec = (DoricLayoutSpec) [it integerValue];
|
||||
}
|
||||
}];
|
||||
|
||||
if ([params isKindOfClass:DoricMarginConfig.class]) {
|
||||
DoricMarginConfig *marginParams = (DoricMarginConfig *) params;
|
||||
NSDictionary *margin = layoutConfig[@"margin"];
|
||||
if (margin) {
|
||||
marginParams.margin = DoricMarginMake(
|
||||
[(NSNumber *) margin[@"left"] floatValue],
|
||||
[(NSNumber *) margin[@"top"] floatValue],
|
||||
[(NSNumber *) margin[@"right"] floatValue],
|
||||
[(NSNumber *) margin[@"bottom"] floatValue]);
|
||||
///View swap index
|
||||
[reused.view removeFromSuperview];
|
||||
[oldNode.view removeFromSuperview];
|
||||
[self.view insertSubview:reused.view atIndex:idx];
|
||||
[self.view insertSubview:oldNode.view atIndex:position];
|
||||
} else {
|
||||
///Not found,insert
|
||||
DoricViewNode *viewNode = [DoricViewNode create:self.doricContext withType:type];
|
||||
viewNode.viewId = viewId;
|
||||
[viewNode initWithSuperNode:self];
|
||||
[viewNode blend:model[@"props"]];
|
||||
[childNodes insertObject:viewNode atIndex:idx];
|
||||
[self.view insertSubview:viewNode.view atIndex:idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/// Insert
|
||||
DoricViewNode *viewNode = [DoricViewNode create:self.doricContext withType:type];
|
||||
if ([viewNode isKindOfClass:[DoricGroupNode class]]) {
|
||||
((DoricGroupNode *) viewNode).reusable = self.reusable;
|
||||
}
|
||||
viewNode.viewId = viewId;
|
||||
[viewNode initWithSuperNode:self];
|
||||
[viewNode blend:model[@"props"]];
|
||||
[childNodes addObject:viewNode];
|
||||
[self.view addSubview:viewNode.view];
|
||||
}
|
||||
}
|
||||
|
||||
NSUInteger count = childNodes.count;
|
||||
for (NSUInteger idx = self.childViewIds.count; idx < count; idx++) {
|
||||
DoricViewNode *viewNode = childNodes.lastObject;
|
||||
[childNodes removeLastObject];
|
||||
[viewNode.view removeFromSuperview];
|
||||
}
|
||||
self.childNodes = [childNodes copy];
|
||||
}
|
||||
|
||||
- (void)blendSubNode:(NSDictionary *)subModel {
|
||||
NSString *viewId = subModel[@"id"];
|
||||
[self.childNodes enumerateObjectsUsingBlock:^(DoricViewNode *obj, NSUInteger idx, BOOL *stop) {
|
||||
if ([viewId isEqualToString:obj.viewId]) {
|
||||
[obj blend:subModel[@"props"]];
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
}
|
||||
@end
|
||||
|
@ -22,5 +22,5 @@
|
||||
|
||||
#import "DoricGroupNode.h"
|
||||
|
||||
@interface DoricHLayoutNode : DoricGroupNode<DoricHLayoutView *, DoricLinearConfig *>
|
||||
@interface DoricHLayoutNode : DoricGroupNode<DoricHLayoutView *>
|
||||
@end
|
||||
|
@ -24,7 +24,7 @@
|
||||
#import "DoricUtil.h"
|
||||
|
||||
@implementation DoricHLayoutNode
|
||||
- (DoricHLayoutView *)build:(NSDictionary *)props {
|
||||
- (DoricHLayoutView *)build {
|
||||
return [DoricHLayoutView new];
|
||||
}
|
||||
|
||||
@ -37,29 +37,4 @@ - (void)blendView:(DoricHLayoutView *)view forPropName:(NSString *)name propValu
|
||||
[super blendView:view forPropName:name propValue:prop];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)blendChild:(DoricViewNode *)child layoutConfig:(NSDictionary *)layoutConfig {
|
||||
[super blendChild:child layoutConfig:layoutConfig];
|
||||
if (![child.layoutConfig isKindOfClass:DoricLinearConfig.class]) {
|
||||
DoricLog(@"blend DoricHLayoutView child error,layout params not match");
|
||||
return;
|
||||
}
|
||||
DoricLinearConfig *params = (DoricLinearConfig *) child.layoutConfig;
|
||||
NSDictionary *margin = layoutConfig[@"margin"];
|
||||
if (margin) {
|
||||
params.margin = DoricMarginMake(
|
||||
[(NSNumber *) margin[@"left"] floatValue],
|
||||
[(NSNumber *) margin[@"top"] floatValue],
|
||||
[(NSNumber *) margin[@"right"] floatValue],
|
||||
[(NSNumber *) margin[@"bottom"] floatValue]);
|
||||
}
|
||||
NSNumber *alignment = layoutConfig[@"alignment"];
|
||||
if (alignment) {
|
||||
params.alignment = (DoricGravity) [alignment integerValue];
|
||||
}
|
||||
}
|
||||
|
||||
- (DoricLinearConfig *)generateDefaultLayoutParams {
|
||||
return [[DoricLinearConfig alloc] init];
|
||||
}
|
||||
@end
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
@implementation DoricImageNode
|
||||
|
||||
- (UIImageView *)build:(NSDictionary *)props {
|
||||
- (UIImageView *)build {
|
||||
return [[UIImageView alloc] init];
|
||||
}
|
||||
|
||||
|
@ -31,9 +31,9 @@ typedef struct DoricMargin DoricMargin;
|
||||
DoricMargin DoricMarginMake(CGFloat left, CGFloat top, CGFloat right, CGFloat bottom);
|
||||
|
||||
typedef NS_ENUM(NSInteger, DoricLayoutSpec) {
|
||||
DoricLayoutExact,
|
||||
DoricLayoutWrapContent,
|
||||
DoricLayoutAtMost,
|
||||
DoricLayoutExact = 0,
|
||||
DoricLayoutWrapContent = 1,
|
||||
DoricLayoutAtMost = 2,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, DoricGravity) {
|
||||
@ -54,43 +54,31 @@ typedef NS_ENUM(NSInteger, DoricGravity) {
|
||||
@interface DoricLayoutConfig : NSObject
|
||||
@property(nonatomic, assign) DoricLayoutSpec widthSpec;
|
||||
@property(nonatomic, assign) DoricLayoutSpec heightSpec;
|
||||
@property(nonatomic) DoricMargin margin;
|
||||
@property(nonatomic, assign) DoricGravity alignment;
|
||||
@property(nonatomic, assign) NSUInteger weight;
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
- (instancetype)initWithWidth:(DoricLayoutSpec)width height:(DoricLayoutSpec)height;
|
||||
|
||||
@end
|
||||
|
||||
@interface DoricStackConfig : DoricLayoutConfig
|
||||
@end
|
||||
|
||||
@interface DoricMarginConfig : DoricLayoutConfig
|
||||
@property(nonatomic) DoricMargin margin;
|
||||
|
||||
- (instancetype)initWithWidth:(DoricLayoutSpec)width height:(DoricLayoutSpec)height margin:(DoricMargin)margin;
|
||||
@end
|
||||
|
||||
@interface DoricLinearConfig : DoricMarginConfig
|
||||
@property(nonatomic, assign) NSUInteger weight;
|
||||
@end
|
||||
|
||||
|
||||
@interface DoricLayoutContainer <T :DoricLayoutConfig *> : UIView
|
||||
|
||||
- (T)configForChild:(__kindof UIView *)child;
|
||||
|
||||
- (void)layout;
|
||||
|
||||
- (void)requestLayout;
|
||||
@end
|
||||
|
||||
@interface DoricStackView : DoricLayoutContainer<DoricStackConfig *>
|
||||
@interface DoricLayoutContainer : UIView
|
||||
@property(nonatomic, assign) DoricGravity gravity;
|
||||
|
||||
- (void)layout:(CGSize)targetSize;
|
||||
|
||||
- (CGSize)sizeContent:(CGSize)size;
|
||||
@end
|
||||
|
||||
@interface DoricLinearView : DoricLayoutContainer<DoricLinearConfig *>
|
||||
@property(nonatomic, assign) DoricGravity gravity;
|
||||
@interface DoricStackView : DoricLayoutContainer
|
||||
@end
|
||||
|
||||
@interface DoricLinearView : DoricLayoutContainer
|
||||
@property(nonatomic, assign) CGFloat space;
|
||||
@end
|
||||
|
||||
|
@ -46,107 +46,70 @@ - (instancetype)initWithWidth:(DoricLayoutSpec)width height:(DoricLayoutSpec)hei
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation DoricMarginConfig
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_margin = DoricMarginMake(0, 0, 0, 0);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithWidth:(DoricLayoutSpec)width height:(DoricLayoutSpec)height margin:(DoricMargin)margin {
|
||||
if (self = [super initWithWidth:width height:height]) {
|
||||
if (self = [super init]) {
|
||||
_widthSpec = width;
|
||||
_heightSpec = height;
|
||||
_margin = margin;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation DoricStackConfig
|
||||
@end
|
||||
|
||||
@implementation DoricLinearConfig
|
||||
@end
|
||||
|
||||
|
||||
@interface DoricLayoutContainer ()
|
||||
@property(nonatomic, assign) BOOL waitingLayout;
|
||||
@property(nonatomic, assign) CGFloat contentWidth;
|
||||
@property(nonatomic, assign) CGFloat contentHeight;
|
||||
@property(nonatomic, assign) NSUInteger contentWeight;
|
||||
@end
|
||||
|
||||
@implementation DoricLayoutContainer
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_waitingLayout = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
_waitingLayout = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
if (self = [super initWithCoder:coder]) {
|
||||
_waitingLayout = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (DoricLayoutConfig *)configForChild:(UIView *)child {
|
||||
DoricLayoutConfig *config = child.layoutConfig;
|
||||
if (!config) {
|
||||
config = [[DoricLayoutConfig alloc] init];
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
- (void)requestLayout {
|
||||
if ([self.superview isKindOfClass:[DoricLinearView class]]) {
|
||||
[(DoricLinearView *) self.superview requestLayout];
|
||||
return;
|
||||
}
|
||||
if (self.waitingLayout) {
|
||||
return;
|
||||
}
|
||||
self.waitingLayout = YES;
|
||||
__weak typeof(self) _self = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__strong typeof(_self) self = _self;
|
||||
[self sizeToFit];
|
||||
[self layout];
|
||||
self.waitingLayout = NO;
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setNeedsLayout {
|
||||
[super setNeedsLayout];
|
||||
if (self.waitingLayout) {
|
||||
return;
|
||||
}
|
||||
[self requestLayout];
|
||||
}
|
||||
|
||||
- (BOOL)waitingLayout {
|
||||
- (void)layoutSubviews {
|
||||
if ([self.superview isKindOfClass:[DoricLayoutContainer class]]) {
|
||||
return [(DoricLayoutContainer *) self.superview waitingLayout];
|
||||
[self.superview layoutSubviews];
|
||||
} else {
|
||||
CGSize size = [self sizeThatFits:CGSizeMake(self.superview.width, self.superview.height)];
|
||||
[self layout:size];
|
||||
}
|
||||
return _waitingLayout;
|
||||
}
|
||||
|
||||
- (void)layout {
|
||||
[self.subviews enumerateObjectsUsingBlock:^(__kindof UIView *child, NSUInteger idx, BOOL *stop) {
|
||||
if ([child isKindOfClass:[DoricLayoutContainer class]]) {
|
||||
[(DoricLayoutContainer *) child layout];
|
||||
}
|
||||
}];
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
CGFloat width = self.width;
|
||||
CGFloat height = self.height;
|
||||
|
||||
DoricLayoutConfig *config = self.layoutConfig;
|
||||
if (!config) {
|
||||
config = [DoricLayoutConfig new];
|
||||
}
|
||||
if (config.widthSpec == DoricLayoutAtMost
|
||||
|| config.widthSpec == DoricLayoutWrapContent) {
|
||||
width = size.width - config.margin.left - config.margin.right;
|
||||
}
|
||||
if (config.heightSpec == DoricLayoutAtMost
|
||||
|| config.heightSpec == DoricLayoutWrapContent) {
|
||||
height = size.height - config.margin.top - config.margin.bottom;
|
||||
}
|
||||
|
||||
CGSize contentSize = [self sizeContent:CGSizeMake(width, height)];
|
||||
if (config.widthSpec == DoricLayoutWrapContent) {
|
||||
width = contentSize.width;
|
||||
}
|
||||
if (config.heightSpec == DoricLayoutWrapContent) {
|
||||
height = contentSize.height;
|
||||
}
|
||||
return CGSizeMake(width, height);
|
||||
}
|
||||
|
||||
- (CGSize)sizeContent:(CGSize)size {
|
||||
return size;
|
||||
}
|
||||
|
||||
- (void)layout:(CGSize)targetSize {
|
||||
self.width = targetSize.width;
|
||||
self.height = targetSize.height;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@ -157,175 +120,223 @@ @interface DoricStackView ()
|
||||
@end
|
||||
|
||||
@implementation DoricStackView
|
||||
- (DoricStackConfig *)configForChild:(UIView *)child {
|
||||
DoricStackConfig *config = (DoricStackConfig *) child.layoutConfig;
|
||||
if (!config) {
|
||||
config = [[DoricStackConfig alloc] init];
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
- (void)sizeToFit {
|
||||
DoricLayoutConfig *config = self.layoutConfig;
|
||||
self.contentWidth = 0;
|
||||
self.contentHeight = 0;
|
||||
- (CGSize)sizeContent:(CGSize)size {
|
||||
CGFloat contentWidth = 0;
|
||||
CGFloat contentHeight = 0;
|
||||
for (UIView *child in self.subviews) {
|
||||
if (child.isHidden) {
|
||||
continue;
|
||||
}
|
||||
DoricStackConfig *childConfig = [self configForChild:child];
|
||||
DoricLayoutConfig *childConfig = child.layoutConfig;
|
||||
if (!childConfig) {
|
||||
childConfig = [DoricLayoutConfig new];
|
||||
}
|
||||
CGSize childSize = CGSizeMake(child.width, child.height);
|
||||
if ([child isKindOfClass:[DoricLayoutContainer class]]
|
||||
|| childConfig.widthSpec == DoricLayoutWrapContent
|
||||
|| childConfig.heightSpec == DoricLayoutWrapContent) {
|
||||
[child sizeToFit];
|
||||
childSize = [child sizeThatFits:CGSizeMake(size.width, size.height - contentHeight)];
|
||||
}
|
||||
self.contentWidth = MAX(self.contentWidth, child.width);
|
||||
self.contentHeight = MAX(self.contentHeight, child.height);
|
||||
}
|
||||
if (config.widthSpec == DoricLayoutWrapContent) {
|
||||
self.width = self.contentWidth;
|
||||
} else if (config.widthSpec == DoricLayoutAtMost) {
|
||||
self.width = self.superview.width;
|
||||
}
|
||||
if (config.heightSpec == DoricLayoutWrapContent) {
|
||||
self.height = self.contentHeight;
|
||||
} else if (config.heightSpec == DoricLayoutAtMost) {
|
||||
self.height = self.superview.height;
|
||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
||||
childSize.width = child.width;
|
||||
} else if (childConfig.widthSpec == DoricLayoutAtMost) {
|
||||
childSize.width = size.width;
|
||||
}
|
||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
||||
childSize.height = child.height;
|
||||
} else if (childConfig.heightSpec == DoricLayoutAtMost) {
|
||||
childSize.height = size.height - contentHeight;
|
||||
}
|
||||
if (childConfig.weight) {
|
||||
childSize.height = child.height;
|
||||
}
|
||||
contentWidth = MAX(contentWidth, childSize.width + childConfig.margin.left + childConfig.margin.right);
|
||||
contentHeight = MAX(contentHeight, childSize.height + childConfig.margin.top + childConfig.margin.bottom);
|
||||
}
|
||||
self.contentWidth = contentWidth;
|
||||
self.contentHeight = contentHeight;
|
||||
return CGSizeMake(contentWidth, contentHeight);
|
||||
}
|
||||
|
||||
- (void)layout {
|
||||
- (void)layout:(CGSize)targetSize {
|
||||
for (UIView *child in self.subviews) {
|
||||
if (child.isHidden) {
|
||||
continue;
|
||||
}
|
||||
DoricStackConfig *childConfig = [self configForChild:child];
|
||||
DoricLayoutConfig *childConfig = child.layoutConfig;
|
||||
if (!childConfig) {
|
||||
childConfig = [DoricLayoutConfig new];
|
||||
}
|
||||
|
||||
CGSize size = [child sizeThatFits:CGSizeMake(targetSize.width, targetSize.height)];
|
||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
||||
size.width = child.width;
|
||||
}
|
||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
||||
size.height = child.height;
|
||||
}
|
||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
||||
size.width = child.width;
|
||||
} else if (childConfig.widthSpec == DoricLayoutAtMost) {
|
||||
size.width = targetSize.width;
|
||||
}
|
||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
||||
size.height = child.height;
|
||||
} else if (childConfig.heightSpec == DoricLayoutAtMost) {
|
||||
size.height = targetSize.height;
|
||||
}
|
||||
child.width = size.width;
|
||||
child.height = size.height;
|
||||
|
||||
DoricGravity gravity = childConfig.alignment | self.gravity;
|
||||
|
||||
if ((gravity & LEFT) == LEFT) {
|
||||
child.left = 0;
|
||||
} else if ((gravity & RIGHT) == RIGHT) {
|
||||
child.right = self.width;
|
||||
child.right = targetSize.width;
|
||||
} else if ((gravity & CENTER_X) == CENTER_X) {
|
||||
child.centerX = self.width / 2;
|
||||
}
|
||||
if ((gravity & TOP) == TOP) {
|
||||
child.top = 0;
|
||||
} else if ((gravity & BOTTOM) == BOTTOM) {
|
||||
child.bottom = self.height;
|
||||
} else if ((gravity & CENTER_Y) == CENTER_Y) {
|
||||
child.centerY = self.height / 2;
|
||||
}
|
||||
if (childConfig.widthSpec == DoricLayoutAtMost) {
|
||||
child.width = self.width;
|
||||
}
|
||||
if (childConfig.heightSpec == DoricLayoutAtMost) {
|
||||
child.height = self.height;
|
||||
}
|
||||
|
||||
if ([child isKindOfClass:[DoricLayoutContainer class]]) {
|
||||
[(DoricLayoutContainer *) child layout];
|
||||
}
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
@interface DoricLinearView ()
|
||||
@property(nonatomic, assign) CGFloat contentWidth;
|
||||
@property(nonatomic, assign) CGFloat contentHeight;
|
||||
@property(nonatomic, assign) NSUInteger contentWeight;
|
||||
@end
|
||||
|
||||
@implementation DoricLinearView
|
||||
- (DoricLinearConfig *)configForChild:(UIView *)child {
|
||||
DoricLinearConfig *config = (DoricLinearConfig *) child.layoutConfig;
|
||||
if (!config) {
|
||||
config = [[DoricLinearConfig alloc] init];
|
||||
}
|
||||
return config;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation DoricVLayoutView
|
||||
|
||||
- (void)sizeToFit {
|
||||
DoricLayoutConfig *config = self.layoutConfig;
|
||||
self.contentWidth = 0;
|
||||
self.contentHeight = 0;
|
||||
self.contentWeight = 0;
|
||||
for (UIView *child in self.subviews) {
|
||||
if (child.isHidden) {
|
||||
continue;
|
||||
}
|
||||
DoricLinearConfig *childConfig = [self configForChild:child];
|
||||
if ([child isKindOfClass:[DoricLayoutContainer class]]
|
||||
|| childConfig.widthSpec == DoricLayoutWrapContent
|
||||
|| childConfig.heightSpec == DoricLayoutWrapContent) {
|
||||
[child sizeToFit];
|
||||
}
|
||||
self.contentWidth = MAX(self.contentWidth, child.width + childConfig.margin.left + childConfig.margin.right);
|
||||
self.contentHeight += child.height + self.space + childConfig.margin.top + childConfig.margin.bottom;
|
||||
self.contentWeight += childConfig.weight;
|
||||
}
|
||||
self.contentHeight -= self.space;
|
||||
if (config.widthSpec == DoricLayoutWrapContent) {
|
||||
self.width = self.contentWidth;
|
||||
} else if (config.widthSpec == DoricLayoutAtMost) {
|
||||
self.width = self.superview.width;
|
||||
}
|
||||
if (config.heightSpec == DoricLayoutWrapContent) {
|
||||
self.height = self.contentHeight;
|
||||
} else if (config.heightSpec == DoricLayoutAtMost) {
|
||||
self.height = self.superview.height;
|
||||
}
|
||||
if (self.contentWeight) {
|
||||
CGFloat remain = self.height - self.contentHeight;
|
||||
for (UIView *child in self.subviews) {
|
||||
if (child.isHidden) {
|
||||
continue;
|
||||
}
|
||||
DoricLinearConfig *childConfig = [self configForChild:child];
|
||||
if (childConfig.weight) {
|
||||
child.height += remain / self.contentWeight * childConfig.weight;
|
||||
}
|
||||
}
|
||||
self.contentHeight = self.height;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layout {
|
||||
CGFloat yStart = 0;
|
||||
if ((self.gravity & TOP) == TOP) {
|
||||
yStart = 0;
|
||||
} else if ((self.gravity & BOTTOM) == BOTTOM) {
|
||||
yStart = self.height - self.contentHeight;
|
||||
} else if ((self.gravity & CENTER_Y) == CENTER_Y) {
|
||||
yStart = (self.height - self.contentHeight) / 2;
|
||||
}
|
||||
for (UIView *child in self.subviews) {
|
||||
if (child.isHidden) {
|
||||
continue;
|
||||
}
|
||||
DoricLinearConfig *childConfig = [self configForChild:child];
|
||||
DoricGravity gravity = childConfig.alignment | self.gravity;
|
||||
if ((gravity & LEFT) == LEFT) {
|
||||
child.left = 0;
|
||||
} else if ((gravity & RIGHT) == RIGHT) {
|
||||
child.right = self.width;
|
||||
} else if ((gravity & CENTER_X) == CENTER_X) {
|
||||
child.centerX = self.width / 2;
|
||||
child.centerX = targetSize.width / 2;
|
||||
} else {
|
||||
if (childConfig.margin.left) {
|
||||
child.left = childConfig.margin.left;
|
||||
} else if (childConfig.margin.right) {
|
||||
child.right = self.width - childConfig.margin.right;
|
||||
child.right = targetSize.width - childConfig.margin.right;
|
||||
}
|
||||
}
|
||||
if (childConfig.widthSpec == DoricLayoutAtMost) {
|
||||
child.width = self.width;
|
||||
|
||||
if ((gravity & TOP) == TOP) {
|
||||
child.top = 0;
|
||||
} else if ((gravity & BOTTOM) == BOTTOM) {
|
||||
child.bottom = targetSize.height;
|
||||
} else if ((gravity & CENTER_Y) == CENTER_Y) {
|
||||
child.centerY = targetSize.height / 2;
|
||||
} else {
|
||||
if (childConfig.margin.top) {
|
||||
child.top = childConfig.margin.top;
|
||||
} else if (childConfig.margin.bottom) {
|
||||
child.bottom = targetSize.height - childConfig.margin.bottom;
|
||||
}
|
||||
}
|
||||
if (childConfig.heightSpec == DoricLayoutAtMost) {
|
||||
child.height = self.height - yStart - childConfig.margin.top - childConfig.margin.bottom - self.space;
|
||||
|
||||
if ([child isKindOfClass:[DoricLayoutContainer class]]) {
|
||||
[(DoricLayoutContainer *) child layout:size];
|
||||
}
|
||||
}
|
||||
self.width = targetSize.width;
|
||||
self.height = targetSize.height;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation DoricLinearView
|
||||
@end
|
||||
|
||||
@implementation DoricVLayoutView
|
||||
- (CGSize)sizeContent:(CGSize)size {
|
||||
CGFloat contentWidth = 0;
|
||||
CGFloat contentHeight = 0;
|
||||
NSUInteger contentWeight = 0;
|
||||
for (UIView *child in self.subviews) {
|
||||
if (child.isHidden) {
|
||||
continue;
|
||||
}
|
||||
DoricLayoutConfig *childConfig = child.layoutConfig;
|
||||
if (!childConfig) {
|
||||
childConfig = [DoricLayoutConfig new];
|
||||
}
|
||||
CGSize childSize = CGSizeMake(child.width, child.height);
|
||||
if ([child isKindOfClass:[DoricLayoutContainer class]]
|
||||
|| childConfig.widthSpec == DoricLayoutWrapContent
|
||||
|| childConfig.heightSpec == DoricLayoutWrapContent) {
|
||||
childSize = [child sizeThatFits:CGSizeMake(size.width, size.height - contentHeight)];
|
||||
}
|
||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
||||
childSize.width = child.width;
|
||||
} else if (childConfig.widthSpec == DoricLayoutAtMost) {
|
||||
childSize.width = size.width;
|
||||
}
|
||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
||||
childSize.height = child.height;
|
||||
} else if (childConfig.heightSpec == DoricLayoutAtMost) {
|
||||
childSize.height = size.height - contentHeight;
|
||||
}
|
||||
if (childConfig.weight) {
|
||||
childSize.height = child.height;
|
||||
}
|
||||
contentWidth = MAX(contentWidth, childSize.width + childConfig.margin.left + childConfig.margin.right);
|
||||
contentHeight += childSize.height + self.space + childConfig.margin.top + childConfig.margin.bottom;
|
||||
contentWeight += childConfig.weight;
|
||||
}
|
||||
contentHeight -= self.space;
|
||||
self.contentWidth = contentWidth;
|
||||
self.contentHeight = contentHeight;
|
||||
self.contentWeight = contentWeight;
|
||||
if (contentWeight) {
|
||||
contentHeight = size.height;
|
||||
}
|
||||
return CGSizeMake(contentWidth, contentHeight);
|
||||
}
|
||||
|
||||
- (void)layout:(CGSize)targetSize {
|
||||
CGFloat yStart = 0;
|
||||
if ((self.gravity & TOP) == TOP) {
|
||||
yStart = 0;
|
||||
} else if ((self.gravity & BOTTOM) == BOTTOM) {
|
||||
yStart = targetSize.height - self.contentHeight;
|
||||
} else if ((self.gravity & CENTER_Y) == CENTER_Y) {
|
||||
yStart = (targetSize.height - self.contentHeight) / 2;
|
||||
}
|
||||
CGFloat remain = targetSize.height - self.contentHeight;
|
||||
for (UIView *child in self.subviews) {
|
||||
if (child.isHidden) {
|
||||
continue;
|
||||
}
|
||||
DoricLayoutConfig *childConfig = child.layoutConfig;
|
||||
if (!childConfig) {
|
||||
childConfig = [DoricLayoutConfig new];
|
||||
}
|
||||
|
||||
CGSize size = [child sizeThatFits:CGSizeMake(targetSize.width, targetSize.height - yStart)];
|
||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
||||
size.width = child.width;
|
||||
}
|
||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
||||
size.height = child.height;
|
||||
}
|
||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
||||
size.width = child.width;
|
||||
} else if (childConfig.widthSpec == DoricLayoutAtMost) {
|
||||
size.width = targetSize.width;
|
||||
}
|
||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
||||
size.height = child.height;
|
||||
} else if (childConfig.heightSpec == DoricLayoutAtMost) {
|
||||
size.height = targetSize.height - yStart;
|
||||
}
|
||||
if (childConfig.weight) {
|
||||
size.height = child.height;
|
||||
}
|
||||
|
||||
if (childConfig.weight) {
|
||||
size.height += remain / self.contentWeight * childConfig.weight;
|
||||
}
|
||||
child.width = size.width;
|
||||
child.height = size.height;
|
||||
|
||||
DoricGravity gravity = childConfig.alignment | self.gravity;
|
||||
|
||||
if ((gravity & LEFT) == LEFT) {
|
||||
child.left = 0;
|
||||
} else if ((gravity & RIGHT) == RIGHT) {
|
||||
child.right = self.width;
|
||||
} else if ((gravity & CENTER_X) == CENTER_X) {
|
||||
child.centerX = targetSize.width / 2;
|
||||
} else {
|
||||
if (childConfig.margin.left) {
|
||||
child.left = childConfig.margin.left;
|
||||
} else if (childConfig.margin.right) {
|
||||
child.right = targetSize.width - childConfig.margin.right;
|
||||
}
|
||||
}
|
||||
if (childConfig.margin.top) {
|
||||
yStart += childConfig.margin.top;
|
||||
@ -336,102 +347,124 @@ - (void)layout {
|
||||
yStart += childConfig.margin.bottom;
|
||||
}
|
||||
if ([child isKindOfClass:[DoricLayoutContainer class]]) {
|
||||
[(DoricLayoutContainer *) child layout];
|
||||
[(DoricLayoutContainer *) child layout:size];
|
||||
}
|
||||
}
|
||||
self.width = targetSize.width;
|
||||
self.height = targetSize.height;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation DoricHLayoutView
|
||||
- (void)sizeToFit {
|
||||
DoricLinearConfig *config;
|
||||
if ([self.superview isKindOfClass:[DoricLinearView class]]) {
|
||||
config = [(DoricLinearView *) self.superview configForChild:self];
|
||||
} else {
|
||||
config = (DoricLinearConfig *) self.layoutConfig;
|
||||
if (!config) {
|
||||
config = [[DoricLinearConfig alloc] init];
|
||||
}
|
||||
}
|
||||
self.contentWidth = 0;
|
||||
self.contentHeight = 0;
|
||||
self.contentWeight = 0;
|
||||
|
||||
- (CGSize)sizeContent:(CGSize)size {
|
||||
CGFloat contentWidth = 0;
|
||||
CGFloat contentHeight = 0;
|
||||
NSUInteger contentWeight = 0;
|
||||
for (UIView *child in self.subviews) {
|
||||
if (child.isHidden) {
|
||||
continue;
|
||||
}
|
||||
DoricLinearConfig *childConfig = [self configForChild:child];
|
||||
DoricLayoutConfig *childConfig = child.layoutConfig;
|
||||
if (!childConfig) {
|
||||
childConfig = [DoricLayoutConfig new];
|
||||
}
|
||||
CGSize childSize = CGSizeMake(child.width, child.height);
|
||||
if ([child isKindOfClass:[DoricLayoutContainer class]]
|
||||
|| childConfig.widthSpec == DoricLayoutWrapContent
|
||||
|| childConfig.heightSpec == DoricLayoutWrapContent) {
|
||||
[child sizeToFit];
|
||||
childSize = [child sizeThatFits:CGSizeMake(size.width - contentWidth, size.height)];
|
||||
}
|
||||
self.contentHeight = MAX(self.contentHeight, child.height + childConfig.margin.top + childConfig.margin.bottom);
|
||||
self.contentWidth += child.width + self.space + childConfig.margin.left + childConfig.margin.right;
|
||||
self.contentWeight += childConfig.weight;
|
||||
}
|
||||
self.contentWidth -= self.space;
|
||||
if (config.widthSpec == DoricLayoutWrapContent) {
|
||||
self.width = self.contentWidth;
|
||||
} else if (config.widthSpec == DoricLayoutAtMost) {
|
||||
self.width = self.superview.width;
|
||||
}
|
||||
if (config.heightSpec == DoricLayoutWrapContent) {
|
||||
self.height = self.contentHeight;
|
||||
} else if (config.heightSpec == DoricLayoutAtMost) {
|
||||
self.height = self.superview.height;
|
||||
}
|
||||
if (self.contentWeight) {
|
||||
CGFloat remain = self.width - self.contentWidth;
|
||||
for (UIView *child in self.subviews) {
|
||||
if (child.isHidden) {
|
||||
continue;
|
||||
}
|
||||
DoricLinearConfig *childConfig = [self configForChild:child];
|
||||
if (childConfig.weight) {
|
||||
child.width += remain / self.contentWeight * childConfig.weight;
|
||||
}
|
||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
||||
childSize.width = child.width;
|
||||
} else if (childConfig.widthSpec == DoricLayoutAtMost) {
|
||||
childSize.width = size.width - contentWidth;
|
||||
}
|
||||
self.contentWidth = self.width;
|
||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
||||
childSize.height = child.height;
|
||||
} else if (childConfig.heightSpec == DoricLayoutAtMost) {
|
||||
childSize.height = size.height;
|
||||
}
|
||||
if (childConfig.weight) {
|
||||
childSize.width = child.width;
|
||||
}
|
||||
contentWidth += childSize.width + self.space + childConfig.margin.left + childConfig.margin.right;
|
||||
contentHeight = MAX(contentHeight, childSize.height + childConfig.margin.top + childConfig.margin.bottom);
|
||||
contentWeight += childConfig.weight;
|
||||
}
|
||||
contentWidth -= self.space;
|
||||
self.contentWidth = contentWidth;
|
||||
self.contentHeight = contentHeight;
|
||||
self.contentWeight = contentWeight;
|
||||
if (contentWeight) {
|
||||
contentWidth = size.width;
|
||||
}
|
||||
return CGSizeMake(contentWidth, contentHeight);
|
||||
}
|
||||
|
||||
- (void)layout {
|
||||
- (void)layout:(CGSize)targetSize {
|
||||
CGFloat xStart = 0;
|
||||
if ((self.gravity & LEFT) == LEFT) {
|
||||
if (self.contentWeight) {
|
||||
xStart = 0;
|
||||
} else if ((self.gravity & LEFT) == LEFT) {
|
||||
xStart = 0;
|
||||
} else if ((self.gravity & RIGHT) == RIGHT) {
|
||||
xStart = self.width - self.contentWidth;
|
||||
xStart = targetSize.width - self.contentWidth;
|
||||
} else if ((self.gravity & CENTER_X) == CENTER_X) {
|
||||
xStart = (self.width - self.contentWidth) / 2;
|
||||
xStart = (targetSize.width - self.contentWidth) / 2;
|
||||
}
|
||||
CGFloat remain = targetSize.width - self.contentWidth;
|
||||
for (UIView *child in self.subviews) {
|
||||
if (child.isHidden) {
|
||||
continue;
|
||||
}
|
||||
DoricLinearConfig *childConfig = [self configForChild:child];
|
||||
DoricLayoutConfig *childConfig = child.layoutConfig;
|
||||
if (!childConfig) {
|
||||
childConfig = [DoricLayoutConfig new];
|
||||
}
|
||||
|
||||
CGSize size = [child sizeThatFits:CGSizeMake(targetSize.width - xStart, targetSize.height)];
|
||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
||||
size.width = child.width;
|
||||
}
|
||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
||||
size.height = child.height;
|
||||
}
|
||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
||||
size.width = child.width;
|
||||
} else if (childConfig.widthSpec == DoricLayoutAtMost) {
|
||||
size.width = targetSize.width - xStart;
|
||||
}
|
||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
||||
size.height = child.height;
|
||||
} else if (childConfig.heightSpec == DoricLayoutAtMost) {
|
||||
size.height = targetSize.height;
|
||||
}
|
||||
if (childConfig.weight) {
|
||||
size.width = child.width;
|
||||
}
|
||||
|
||||
if (childConfig.weight) {
|
||||
size.width += remain / self.contentWeight * childConfig.weight;
|
||||
}
|
||||
child.width = size.width;
|
||||
child.height = size.height;
|
||||
|
||||
DoricGravity gravity = childConfig.alignment | self.gravity;
|
||||
if ((gravity & TOP) == TOP) {
|
||||
child.top = 0;
|
||||
} else if ((gravity & BOTTOM) == BOTTOM) {
|
||||
child.bottom = self.height;
|
||||
child.bottom = targetSize.height;
|
||||
} else if ((gravity & CENTER_Y) == CENTER_Y) {
|
||||
child.centerY = self.height / 2;
|
||||
child.centerY = targetSize.height / 2;
|
||||
} else {
|
||||
if (childConfig.margin.top) {
|
||||
child.top = childConfig.margin.top;
|
||||
} else if (childConfig.margin.bottom) {
|
||||
child.bottom = self.height - childConfig.margin.bottom;
|
||||
child.bottom = targetSize.height - childConfig.margin.bottom;
|
||||
}
|
||||
}
|
||||
|
||||
if (childConfig.heightSpec == DoricLayoutAtMost) {
|
||||
child.height = self.height;
|
||||
}
|
||||
if (childConfig.widthSpec == DoricLayoutAtMost) {
|
||||
child.width = self.width - xStart - childConfig.margin.right - childConfig.margin.left - self.space;
|
||||
}
|
||||
|
||||
if (childConfig.margin.left) {
|
||||
xStart += childConfig.margin.left;
|
||||
}
|
||||
@ -441,9 +474,11 @@ - (void)layout {
|
||||
xStart += childConfig.margin.right;
|
||||
}
|
||||
if ([child isKindOfClass:[DoricLayoutContainer class]]) {
|
||||
[(DoricLayoutContainer *) child layout];
|
||||
[(DoricLayoutContainer *) child layout:size];
|
||||
}
|
||||
}
|
||||
self.width = targetSize.width;
|
||||
self.height = targetSize.height;
|
||||
}
|
||||
@end
|
||||
|
||||
|
25
iOS/Pod/Classes/Shader/DoricListItemNode.h
Normal file
25
iOS/Pod/Classes/Shader/DoricListItemNode.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
//
|
||||
// Created by pengfei.zhou on 2019/11/15.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "DoricStackNode.h"
|
||||
|
||||
@interface DoricListItemNode : DoricStackNode
|
||||
@end
|
44
iOS/Pod/Classes/Shader/DoricListItemNode.m
Normal file
44
iOS/Pod/Classes/Shader/DoricListItemNode.m
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
//
|
||||
// Created by pengfei.zhou on 2019/11/15.
|
||||
//
|
||||
|
||||
#import "DoricListItemNode.h"
|
||||
#import "DoricExtensions.h"
|
||||
|
||||
@interface DoricListItemNode ()
|
||||
@end
|
||||
|
||||
@interface DoricListItemView : DoricStackView
|
||||
@end
|
||||
|
||||
@implementation DoricListItemView
|
||||
@end
|
||||
|
||||
|
||||
@implementation DoricListItemNode
|
||||
- (instancetype)initWithContext:(DoricContext *)doricContext {
|
||||
if (self = [super initWithContext:doricContext]) {
|
||||
self.reusable = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (DoricStackView *)build {
|
||||
return [DoricListItemView new];
|
||||
}
|
||||
@end
|
24
iOS/Pod/Classes/Shader/DoricListNode.h
Normal file
24
iOS/Pod/Classes/Shader/DoricListNode.h
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
//
|
||||
// Created by pengfei.zhou on 2019/11/15.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "DoricSuperNode.h"
|
||||
|
||||
@interface DoricListNode : DoricSuperNode<UITableView *>
|
||||
@end
|
153
iOS/Pod/Classes/Shader/DoricListNode.m
Normal file
153
iOS/Pod/Classes/Shader/DoricListNode.m
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
//
|
||||
// Created by pengfei.zhou on 2019/11/15.
|
||||
//
|
||||
|
||||
#import <JavaScriptCore/JavaScriptCore.h>
|
||||
#import "DoricListNode.h"
|
||||
#import "DoricExtensions.h"
|
||||
#import "DoricListItemNode.h"
|
||||
|
||||
@interface DoricTableViewCell : UITableViewCell
|
||||
@property(nonatomic, strong) DoricListItemNode *doricListItemNode;
|
||||
@end
|
||||
|
||||
@implementation DoricTableViewCell
|
||||
@end
|
||||
|
||||
@interface DoricListNode () <UITableViewDataSource, UITableViewDelegate>
|
||||
@property(nonatomic, strong) NSMutableDictionary <NSNumber *, NSString *> *itemViewIds;
|
||||
@property(nonatomic, strong) NSMutableDictionary <NSNumber *, NSNumber *> *itemHeights;
|
||||
@property(nonatomic, assign) NSUInteger itemCount;
|
||||
@property(nonatomic, assign) NSUInteger batchCount;
|
||||
@end
|
||||
|
||||
@implementation DoricListNode
|
||||
- (instancetype)initWithContext:(DoricContext *)doricContext {
|
||||
if (self = [super initWithContext:doricContext]) {
|
||||
_itemViewIds = [NSMutableDictionary new];
|
||||
_itemHeights = [NSMutableDictionary new];
|
||||
_batchCount = 15;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (UITableView *)build {
|
||||
return [[UITableView new] also:^(UITableView *it) {
|
||||
it.dataSource = self;
|
||||
it.delegate = self;
|
||||
it.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)blendView:(UITableView *)view forPropName:(NSString *)name propValue:(id)prop {
|
||||
if ([@"itemCount" isEqualToString:name]) {
|
||||
self.itemCount = [prop unsignedIntegerValue];
|
||||
} else if ([@"renderItem" isEqualToString:name]) {
|
||||
[self.itemViewIds removeAllObjects];
|
||||
[self clearSubModel];
|
||||
} else if ([@"batchCount" isEqualToString:name]) {
|
||||
self.batchCount = [prop unsignedIntegerValue];
|
||||
} else {
|
||||
[super blendView:view forPropName:name propValue:prop];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)blend:(NSDictionary *)props {
|
||||
[super blend:props];
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return self.itemCount;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSUInteger position = (NSUInteger) indexPath.row;
|
||||
NSDictionary *model = [self itemModelAt:position];
|
||||
NSDictionary *props = model[@"props"];
|
||||
NSString *reuseId = props[@"identifier"];
|
||||
|
||||
DoricTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId ?: @"doriccell"];
|
||||
if (!cell) {
|
||||
cell = [[DoricTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseId ?: @"doriccell"];
|
||||
DoricListItemNode *listItemNode = [[DoricListItemNode alloc] initWithContext:self.doricContext];
|
||||
[listItemNode initWithSuperNode:self];
|
||||
cell.doricListItemNode = listItemNode;
|
||||
[cell.contentView addSubview:listItemNode.view];
|
||||
}
|
||||
DoricListItemNode *node = cell.doricListItemNode;
|
||||
node.viewId = model[@"id"];
|
||||
[node blend:props];
|
||||
CGSize size = [node.view sizeThatFits:CGSizeMake(cell.width, cell.height)];
|
||||
[self callItem:position height:size.height];
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSUInteger position = (NSUInteger) indexPath.row;
|
||||
NSNumber *heightNumber = self.itemHeights[@(position)];
|
||||
if (heightNumber) {
|
||||
return [heightNumber floatValue];
|
||||
} else {
|
||||
return 44.f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (NSDictionary *)itemModelAt:(NSUInteger)position {
|
||||
NSString *viewId = self.itemViewIds[@(position)];
|
||||
if (viewId && viewId.length > 0) {
|
||||
return [self subModelOf:viewId];
|
||||
} else {
|
||||
DoricAsyncResult *result = [self callJSResponse:@"renderBunchedItems", @(position), @(self.batchCount), nil];
|
||||
JSValue *models = [result waitUntilResult];
|
||||
NSArray *array = [models toArray];
|
||||
[array enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) {
|
||||
NSString *thisViewId = obj[@"id"];
|
||||
[self setSubModel:obj in:thisViewId];
|
||||
NSUInteger pos = position + idx;
|
||||
self.itemViewIds[@(pos)] = thisViewId;
|
||||
}];
|
||||
return array[0];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)blendSubNode:(NSDictionary *)subModel {
|
||||
NSString *viewId = subModel[@"id"];
|
||||
[self.itemViewIds enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) {
|
||||
if ([viewId isEqualToString:obj]) {
|
||||
*stop = YES;
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[key integerValue] inSection:0];
|
||||
[UIView performWithoutAnimation:^{
|
||||
[self.view reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)callItem:(NSUInteger)position height:(CGFloat)height {
|
||||
NSNumber *old = self.itemHeights[@(position)];
|
||||
if (old && old.floatValue == height) {
|
||||
return;
|
||||
}
|
||||
self.itemHeights[@(position)] = @(height);
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:position inSection:0];
|
||||
[UIView performWithoutAnimation:^{
|
||||
[self.view reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
||||
}];
|
||||
}
|
||||
@end
|
@ -25,13 +25,12 @@
|
||||
@implementation DoricRootNode
|
||||
- (void)setupRootView:(DoricStackView *)view {
|
||||
self.view = view;
|
||||
self.layoutConfig = view.layoutConfig;
|
||||
}
|
||||
|
||||
- (void)render:(NSDictionary *)props {
|
||||
[self blend:props];
|
||||
}
|
||||
- (void)requestLayout {
|
||||
[self.view requestLayout];
|
||||
[self.view setNeedsLayout];
|
||||
}
|
||||
@end
|
||||
|
@ -22,5 +22,5 @@
|
||||
|
||||
#import "DoricGroupNode.h"
|
||||
|
||||
@interface DoricStackNode : DoricGroupNode<DoricStackView *, DoricStackConfig *>
|
||||
@interface DoricStackNode : DoricGroupNode<DoricStackView *>
|
||||
@end
|
||||
|
@ -21,11 +21,10 @@
|
||||
//
|
||||
|
||||
#import "DoricStackNode.h"
|
||||
#import "DoricUtil.h"
|
||||
|
||||
@implementation DoricStackNode
|
||||
|
||||
- (DoricStackView *)build:(NSDictionary *)props {
|
||||
- (DoricStackView *)build {
|
||||
return [DoricStackView new];
|
||||
}
|
||||
|
||||
@ -36,22 +35,4 @@ - (void)blendView:(DoricStackView *)view forPropName:(NSString *)name propValue:
|
||||
[super blendView:view forPropName:name propValue:prop];
|
||||
}
|
||||
}
|
||||
|
||||
- (DoricStackConfig *)generateDefaultLayoutParams {
|
||||
return [[DoricStackConfig alloc] init];
|
||||
}
|
||||
|
||||
- (void)blendChild:(DoricViewNode *)child layoutConfig:(NSDictionary *)layoutConfig {
|
||||
[super blendChild:child layoutConfig:layoutConfig];
|
||||
if (![child.layoutConfig isKindOfClass:DoricStackConfig.class]) {
|
||||
DoricLog(@"blend DoricHLayoutView child error,layout params not match");
|
||||
return;
|
||||
}
|
||||
DoricStackConfig *params = (DoricStackConfig *) child.layoutConfig;
|
||||
NSNumber *alignment = layoutConfig[@"alignment"];
|
||||
if (alignment) {
|
||||
params.alignment = (DoricGravity) [alignment integerValue];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
35
iOS/Pod/Classes/Shader/DoricSuperNode.h
Normal file
35
iOS/Pod/Classes/Shader/DoricSuperNode.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
//
|
||||
// Created by pengfei.zhou on 2019/11/15.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "DoricViewNode.h"
|
||||
|
||||
@interface DoricSuperNode<V:UIView *> : DoricViewNode<V>
|
||||
- (DoricLayoutConfig *)generateDefaultLayoutParams;
|
||||
|
||||
- (void)blendSubNode:(DoricViewNode *)subNode layoutConfig:(NSDictionary *)layoutConfig;
|
||||
|
||||
- (void)blendSubNode:(NSDictionary *)subModel;
|
||||
|
||||
- (NSDictionary *)subModelOf:(NSString *)viewId;
|
||||
|
||||
- (void)setSubModel:(NSDictionary *)model in:(NSString *)viewId;
|
||||
|
||||
- (void)clearSubModel;
|
||||
@end
|
122
iOS/Pod/Classes/Shader/DoricSuperNode.m
Normal file
122
iOS/Pod/Classes/Shader/DoricSuperNode.m
Normal file
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
//
|
||||
// Created by pengfei.zhou on 2019/11/15.
|
||||
//
|
||||
|
||||
#import "DoricSuperNode.h"
|
||||
#import "DoricExtensions.h"
|
||||
|
||||
@interface DoricSuperNode ()
|
||||
@property(nonatomic, strong) NSMutableDictionary <NSString *, NSMutableDictionary *> *subNodes;
|
||||
@end
|
||||
|
||||
@implementation DoricSuperNode
|
||||
- (instancetype)initWithContext:(DoricContext *)doricContext {
|
||||
if (self = [super initWithContext:doricContext]) {
|
||||
_subNodes = [NSMutableDictionary new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)blendView:(UIView *)view forPropName:(NSString *)name propValue:(id)prop {
|
||||
if ([@"subviews" isEqualToString:name]) {
|
||||
NSArray *subviews = prop;
|
||||
for (NSDictionary *subModel in subviews) {
|
||||
[self mixinSubNode:subModel];
|
||||
[self blendSubNode:subModel];
|
||||
}
|
||||
} else {
|
||||
[super blendView:view forPropName:name propValue:prop];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mixinSubNode:(NSDictionary *)dictionary {
|
||||
NSString *viewId = dictionary[@"id"];
|
||||
NSMutableDictionary *oldModel = self.subNodes[viewId];
|
||||
if (oldModel) {
|
||||
[self mixin:dictionary to:oldModel];
|
||||
} else {
|
||||
self.subNodes[viewId] = [dictionary mutableCopy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mixin:(NSDictionary *)srcModel to:(NSMutableDictionary *)targetModel {
|
||||
NSDictionary *srcProp = srcModel[@"props"];
|
||||
NSMutableDictionary *targetProp = [targetModel[@"props"] mutableCopy];
|
||||
[srcProp enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
|
||||
if (![@"subviews" isEqualToString:key]) {
|
||||
targetProp[key] = obj;
|
||||
}
|
||||
}];
|
||||
targetModel[@"props"] = [targetProp copy];
|
||||
}
|
||||
|
||||
- (void)blendSubNode:(DoricViewNode *)subNode layoutConfig:(NSDictionary *)layoutConfig {
|
||||
DoricLayoutConfig *params = subNode.layoutConfig;
|
||||
|
||||
[layoutConfig[@"widthSpec"] also:^(NSNumber *it) {
|
||||
if (it) {
|
||||
params.widthSpec = (DoricLayoutSpec) [it integerValue];
|
||||
}
|
||||
}];
|
||||
|
||||
[layoutConfig[@"heightSpec"] also:^(NSNumber *it) {
|
||||
if (it) {
|
||||
params.heightSpec = (DoricLayoutSpec) [it integerValue];
|
||||
}
|
||||
}];
|
||||
|
||||
NSDictionary *margin = layoutConfig[@"margin"];
|
||||
if (margin) {
|
||||
params.margin = DoricMarginMake(
|
||||
[(NSNumber *) margin[@"left"] floatValue],
|
||||
[(NSNumber *) margin[@"top"] floatValue],
|
||||
[(NSNumber *) margin[@"right"] floatValue],
|
||||
[(NSNumber *) margin[@"bottom"] floatValue]);
|
||||
}
|
||||
|
||||
NSNumber *alignment = layoutConfig[@"alignment"];
|
||||
if (alignment) {
|
||||
params.alignment = (DoricGravity) [alignment integerValue];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)blendSubNode:(NSDictionary *)subModel {
|
||||
NSAssert(NO, @"Should override class:%@ ,method:%@.", NSStringFromClass([self class]),
|
||||
NSStringFromSelector(_cmd));
|
||||
}
|
||||
|
||||
- (DoricLayoutConfig *)generateDefaultLayoutParams {
|
||||
DoricLayoutConfig *params = [[DoricLayoutConfig alloc] init];
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
- (NSDictionary *)subModelOf:(NSString *)viewId {
|
||||
return self.subNodes[viewId];
|
||||
}
|
||||
|
||||
- (void)setSubModel:(NSDictionary *)model in:(NSString *)viewId {
|
||||
self.subNodes[viewId] = [model mutableCopy];
|
||||
}
|
||||
|
||||
- (void)clearSubModel {
|
||||
[self.subNodes removeAllObjects];
|
||||
}
|
||||
|
||||
@end
|
@ -25,7 +25,7 @@
|
||||
#import "DoricGroupNode.h"
|
||||
|
||||
@implementation DoricTextNode
|
||||
- (id)build:(NSDictionary *)props {
|
||||
- (UILabel *)build {
|
||||
return [[UILabel alloc] init];
|
||||
}
|
||||
|
||||
|
@ -22,5 +22,5 @@
|
||||
|
||||
#import "DoricGroupNode.h"
|
||||
|
||||
@interface DoricVLayoutNode : DoricGroupNode<DoricVLayoutView *, DoricLinearConfig *>
|
||||
@interface DoricVLayoutNode : DoricGroupNode<DoricVLayoutView *>
|
||||
@end
|
||||
|
@ -21,11 +21,10 @@
|
||||
//
|
||||
|
||||
#import "DoricVLayoutNode.h"
|
||||
#import "DoricUtil.h"
|
||||
|
||||
@implementation DoricVLayoutNode
|
||||
|
||||
- (DoricVLayoutView *)build:(NSDictionary *)props {
|
||||
- (DoricVLayoutView *)build {
|
||||
return [DoricVLayoutView new];
|
||||
}
|
||||
|
||||
@ -39,28 +38,4 @@ - (void)blendView:(DoricVLayoutView *)view forPropName:(NSString *)name propValu
|
||||
}
|
||||
}
|
||||
|
||||
- (void)blendChild:(DoricViewNode *)child layoutConfig:(NSDictionary *)layoutconfig {
|
||||
[super blendChild:child layoutConfig:layoutconfig];
|
||||
if (![child.layoutConfig isKindOfClass:DoricLinearConfig.class]) {
|
||||
DoricLog(@"blend DoricVLayoutView child error,layout params not match");
|
||||
return;
|
||||
}
|
||||
DoricLinearConfig *params = (DoricLinearConfig *) child.layoutConfig;
|
||||
NSDictionary *margin = layoutconfig[@"margin"];
|
||||
if (margin) {
|
||||
params.margin = DoricMarginMake(
|
||||
[(NSNumber *) margin[@"left"] floatValue],
|
||||
[(NSNumber *) margin[@"top"] floatValue],
|
||||
[(NSNumber *) margin[@"right"] floatValue],
|
||||
[(NSNumber *) margin[@"bottom"] floatValue]);
|
||||
}
|
||||
NSNumber *alignment = layoutconfig[@"alignment"];
|
||||
if (alignment) {
|
||||
params.alignment = (DoricGravity) [alignment integerValue];
|
||||
}
|
||||
}
|
||||
|
||||
- (DoricLinearConfig *)generateDefaultLayoutParams {
|
||||
return [[DoricLinearConfig alloc] init];
|
||||
}
|
||||
@end
|
||||
|
@ -25,30 +25,33 @@
|
||||
#import "UIView+Doric.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@class DoricGroupNode;
|
||||
@class DoricSuperNode;
|
||||
|
||||
@interface DoricViewNode <V:UIView *> : DoricContextHolder
|
||||
|
||||
@property(nonatomic, strong) V view;
|
||||
|
||||
@property(nonatomic, weak) DoricGroupNode *parent;
|
||||
@property(nonatomic, weak) DoricSuperNode *superNode;
|
||||
@property(nonatomic) NSInteger index;
|
||||
|
||||
@property(nonatomic, strong) NSString *viewId;
|
||||
@property(nonatomic, copy) NSString *viewId;
|
||||
|
||||
@property(nonatomic, strong) DoricLayoutConfig *layoutConfig;
|
||||
@property(nonatomic, copy) NSString *type;
|
||||
|
||||
@property(nonatomic, strong, readonly) NSArray<NSString *> *idList;
|
||||
@property(nonatomic, readonly) DoricLayoutConfig *layoutConfig;
|
||||
|
||||
- (V)build:(NSDictionary *)props;
|
||||
@property(nonatomic, readonly) NSArray<NSString *> *idList;
|
||||
|
||||
- (void)initWithSuperNode:(DoricSuperNode *)superNode;
|
||||
|
||||
- (V)build;
|
||||
|
||||
- (void)blend:(NSDictionary *)props;
|
||||
|
||||
- (void)blendView:(V)view forPropName:(NSString *)name propValue:(id)prop;
|
||||
|
||||
- (void)callJSResponse:(NSString *)funcId, ...;
|
||||
- (DoricAsyncResult *)callJSResponse:(NSString *)funcId, ...;
|
||||
|
||||
+ (DoricViewNode *)create:(DoricContext *)context withType:(NSString *)type;
|
||||
+ (__kindof DoricViewNode *)create:(DoricContext *)context withType:(NSString *)type;
|
||||
|
||||
- (void)requestLayout;
|
||||
@end
|
||||
|
@ -25,6 +25,8 @@
|
||||
#import "DoricGroupNode.h"
|
||||
#import "DoricRootNode.h"
|
||||
#import "DoricConstant.h"
|
||||
#import "DoricSuperNode.h"
|
||||
#import "DoricExtensions.h"
|
||||
|
||||
void DoricAddEllipticArcPath(CGMutablePathRef path,
|
||||
CGPoint origin,
|
||||
@ -77,14 +79,23 @@ - (instancetype)initWithContext:(DoricContext *)doricContext {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (UIView *)build:(NSDictionary *)props {
|
||||
|
||||
- (void)initWithSuperNode:(DoricSuperNode *)superNode {
|
||||
self.superNode = superNode;
|
||||
self.view = [[self build] also:^(UIView *it) {
|
||||
it.layoutConfig = [superNode generateDefaultLayoutParams];
|
||||
}];
|
||||
}
|
||||
|
||||
- (DoricLayoutConfig *)layoutConfig {
|
||||
return self.view.layoutConfig;
|
||||
}
|
||||
|
||||
- (UIView *)build {
|
||||
return [[UIView alloc] init];
|
||||
}
|
||||
|
||||
- (void)blend:(NSDictionary *)props {
|
||||
if (self.view == nil) {
|
||||
self.view = [self build:props];
|
||||
}
|
||||
self.view.layoutConfig = self.layoutConfig;
|
||||
for (NSString *key in props) {
|
||||
id value = props[key];
|
||||
@ -110,8 +121,8 @@ - (void)blendView:(UIView *)view forPropName:(NSString *)name propValue:(id)prop
|
||||
} else if ([name isEqualToString:@"bgColor"]) {
|
||||
view.backgroundColor = DoricColor(prop);
|
||||
} else if ([name isEqualToString:@"layoutConfig"]) {
|
||||
if (self.parent && [prop isKindOfClass:[NSDictionary class]]) {
|
||||
[self.parent blendChild:self layoutConfig:prop];
|
||||
if (self.superNode && [prop isKindOfClass:[NSDictionary class]]) {
|
||||
[self.superNode blendSubNode:self layoutConfig:prop];
|
||||
}
|
||||
} else if ([name isEqualToString:@"onClick"]) {
|
||||
self.callbackIds[@"onClick"] = prop;
|
||||
@ -176,13 +187,13 @@ - (void)onClick:(UIView *)view {
|
||||
DoricViewNode *node = self;
|
||||
do {
|
||||
[ret addObject:node.viewId];
|
||||
node = node.parent;
|
||||
node = node.superNode;
|
||||
} while (node && ![node isKindOfClass:[DoricRootNode class]]);
|
||||
|
||||
return [[ret reverseObjectEnumerator] allObjects];
|
||||
}
|
||||
|
||||
- (void)callJSResponse:(NSString *)funcId, ... {
|
||||
- (DoricAsyncResult *)callJSResponse:(NSString *)funcId, ... {
|
||||
NSMutableArray *array = [[NSMutableArray alloc] init];
|
||||
[array addObject:self.idList];
|
||||
[array addObject:funcId];
|
||||
@ -192,18 +203,21 @@ - (void)callJSResponse:(NSString *)funcId, ... {
|
||||
while ((arg = va_arg(args, id)) != nil) {
|
||||
[array addObject:arg];
|
||||
}
|
||||
[self.doricContext callEntity:DORIC_ENTITY_RESPONSE withArgumentsArray:array];
|
||||
DoricAsyncResult *ret = [self.doricContext callEntity:DORIC_ENTITY_RESPONSE withArgumentsArray:array];
|
||||
va_end(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
+ (DoricViewNode *)create:(DoricContext *)context withType:(NSString *)type {
|
||||
+ (__kindof DoricViewNode *)create:(DoricContext *)context withType:(NSString *)type {
|
||||
DoricRegistry *registry = context.driver.registry;
|
||||
Class clz = [registry acquireViewNode:type];
|
||||
return [(DoricViewNode *) [clz alloc] initWithContext:context];
|
||||
DoricViewNode *viewNode = [(DoricViewNode *) [clz alloc] initWithContext:context];
|
||||
viewNode.type = type;
|
||||
return viewNode;
|
||||
}
|
||||
|
||||
- (void)requestLayout {
|
||||
[self.parent requestLayout];
|
||||
[self.superNode requestLayout];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -43,6 +43,8 @@ typedef void(^DoricFinishCallback)(void);
|
||||
- (BOOL)hasResult;
|
||||
|
||||
- (R)getResult;
|
||||
|
||||
- (R)waitUntilResult;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@ -22,31 +22,32 @@
|
||||
|
||||
#import "DoricAsyncResult.h"
|
||||
|
||||
@interface DoricAsyncResult()
|
||||
@property(nonatomic,strong) id result;
|
||||
@interface DoricAsyncResult ()
|
||||
@property(nonatomic, strong) id result;
|
||||
@end
|
||||
|
||||
@implementation DoricAsyncResult
|
||||
|
||||
- (void)setupResult:(id)result {
|
||||
self.result = result;
|
||||
if(self.resultCallback){
|
||||
if (self.resultCallback) {
|
||||
self.resultCallback(result);
|
||||
}
|
||||
if(self.finishCallback){
|
||||
if (self.finishCallback) {
|
||||
self.finishCallback();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupError:(NSException *)exception {
|
||||
self.result = exception;
|
||||
if(self.exceptionCallback){
|
||||
if (self.exceptionCallback) {
|
||||
self.exceptionCallback(exception);
|
||||
}
|
||||
if(self.finishCallback){
|
||||
if (self.finishCallback) {
|
||||
self.finishCallback();
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)hasResult {
|
||||
return self.result;
|
||||
}
|
||||
@ -57,22 +58,35 @@ - (id)getResult {
|
||||
|
||||
- (void)setResultCallback:(DoricResultCallback)callback {
|
||||
_resultCallback = callback;
|
||||
if(self.result && ![self.result isKindOfClass: [NSException class]]){
|
||||
if (self.result && ![self.result isKindOfClass:[NSException class]]) {
|
||||
callback(self.result);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setExceptionCallback:(DoricExceptionCallback)exceptionCallback {
|
||||
_exceptionCallback = exceptionCallback;
|
||||
if([self.result isKindOfClass: [NSException class]]){
|
||||
if ([self.result isKindOfClass:[NSException class]]) {
|
||||
exceptionCallback(self.result);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFinishCallback:(DoricFinishCallback)callback {
|
||||
_finishCallback = callback;
|
||||
if(self.result){
|
||||
if (self.result) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
- (id)waitUntilResult {
|
||||
if (self.result) {
|
||||
return self.result;
|
||||
}
|
||||
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
self.resultCallback = ^(id r) {
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
};
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
return self.result;
|
||||
}
|
||||
@end
|
||||
|
@ -26,7 +26,7 @@ void DoricLog(NSString *_Nonnull format, ...);
|
||||
|
||||
UIColor *_Nonnull DoricColor(NSNumber *_Nonnull number);
|
||||
|
||||
NSBundle *DoricBundle();
|
||||
NSBundle *_Nonnull DoricBundle(void);
|
||||
|
||||
#ifndef DC_LOCK
|
||||
#define DC_LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
|
||||
|
@ -14,6 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from "./src/ui/view"
|
||||
export * from "./src/ui/layout"
|
||||
export * from "./src/ui/listview"
|
||||
export * from "./src/ui/widgets"
|
||||
export * from "./src/ui/panel"
|
||||
export * from "./src/ui/declarative"
|
||||
export * from "./src/util/color"
|
||||
|
@ -1,51 +1,23 @@
|
||||
import { Text, Image, HLayout, VLayout, Stack, LayoutConfig, View } from './view'
|
||||
import { Color, GradientColor } from '../util/color'
|
||||
import { Gravity } from '../util/gravity'
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { View, LayoutSpec } from './view'
|
||||
import { Stack, HLayout, VLayout } from './layout'
|
||||
import { IText, IImage, Text, Image } from './widgets'
|
||||
import { IList, List } from './listview'
|
||||
|
||||
export interface IView {
|
||||
width?: number
|
||||
height?: number
|
||||
bgColor?: Color | GradientColor
|
||||
corners?: number | { leftTop?: number; rightTop?: number; leftBottom?: number; rightBottom?: number }
|
||||
border?: { width: number; color: Color; }
|
||||
shadow?: { color: Color; opacity: number; radius: number; offsetX: number; offsetY: number }
|
||||
alpha?: number
|
||||
hidden?: boolean
|
||||
padding?: {
|
||||
left?: number,
|
||||
right?: number,
|
||||
top?: number,
|
||||
bottom?: number,
|
||||
}
|
||||
layoutConfig?: LayoutConfig
|
||||
onClick?: Function
|
||||
identifier?: string
|
||||
}
|
||||
export interface IText extends IView {
|
||||
text?: string
|
||||
textColor?: Color
|
||||
textSize?: number
|
||||
maxLines?: number
|
||||
textAlignment?: Gravity
|
||||
}
|
||||
|
||||
export interface IImage extends IView {
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
export interface IStack extends IView {
|
||||
gravity?: Gravity
|
||||
}
|
||||
|
||||
export interface IVLayout extends IView {
|
||||
space?: number
|
||||
gravity?: Gravity
|
||||
}
|
||||
|
||||
export interface IHLayout extends IView {
|
||||
space?: number
|
||||
gravity?: Gravity
|
||||
}
|
||||
export function text(config: IText) {
|
||||
const ret = new Text
|
||||
for (let key in config) {
|
||||
@ -64,6 +36,10 @@ export function image(config: IImage) {
|
||||
|
||||
export function stack(views: View[]) {
|
||||
const ret = new Stack
|
||||
ret.layoutConfig = {
|
||||
widthSpec: LayoutSpec.WRAP_CONTENT,
|
||||
heightSpec: LayoutSpec.WRAP_CONTENT,
|
||||
}
|
||||
for (let v of views) {
|
||||
ret.addChild(v)
|
||||
}
|
||||
@ -72,6 +48,10 @@ export function stack(views: View[]) {
|
||||
|
||||
export function hlayout(views: View[]) {
|
||||
const ret = new HLayout
|
||||
ret.layoutConfig = {
|
||||
widthSpec: LayoutSpec.WRAP_CONTENT,
|
||||
heightSpec: LayoutSpec.WRAP_CONTENT,
|
||||
}
|
||||
for (let v of views) {
|
||||
ret.addChild(v)
|
||||
}
|
||||
@ -80,8 +60,20 @@ export function hlayout(views: View[]) {
|
||||
|
||||
export function vlayout(views: View[]) {
|
||||
const ret = new VLayout
|
||||
ret.layoutConfig = {
|
||||
widthSpec: LayoutSpec.WRAP_CONTENT,
|
||||
heightSpec: LayoutSpec.WRAP_CONTENT,
|
||||
}
|
||||
for (let v of views) {
|
||||
ret.addChild(v)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
export function list(config: IList) {
|
||||
const ret = new List
|
||||
for (let key in config) {
|
||||
Reflect.set(ret, key, Reflect.get(config, key, config), ret)
|
||||
}
|
||||
return ret
|
||||
}
|
56
js-framework/src/ui/layout.ts
Normal file
56
js-framework/src/ui/layout.ts
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { LayoutConfig, Group, Property, IView } from "./view";
|
||||
import { Gravity } from "../util/gravity";
|
||||
|
||||
export interface IStack extends IView {
|
||||
gravity?: Gravity
|
||||
}
|
||||
|
||||
export class Stack extends Group implements IStack {
|
||||
@Property
|
||||
gravity?: Gravity
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class Root extends Stack {
|
||||
|
||||
}
|
||||
class LinearLayout extends Group {
|
||||
@Property
|
||||
space?: number
|
||||
|
||||
@Property
|
||||
gravity?: Gravity
|
||||
}
|
||||
|
||||
export interface IVLayout extends IView {
|
||||
space?: number
|
||||
gravity?: Gravity
|
||||
}
|
||||
|
||||
export class VLayout extends LinearLayout implements VLayout {
|
||||
}
|
||||
|
||||
|
||||
export interface IHLayout extends IView {
|
||||
space?: number
|
||||
gravity?: Gravity
|
||||
}
|
||||
|
||||
export class HLayout extends LinearLayout implements IHLayout {
|
||||
}
|
86
js-framework/src/ui/listview.ts
Normal file
86
js-framework/src/ui/listview.ts
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { View, Property, LayoutSpec, Superview, IView } from "./view";
|
||||
import { Stack } from "./layout";
|
||||
|
||||
export function listItem(item: View) {
|
||||
return (new ListItem).also((it) => {
|
||||
it.layoutConfig = {
|
||||
widthSpec: LayoutSpec.WRAP_CONTENT,
|
||||
heightSpec: LayoutSpec.WRAP_CONTENT,
|
||||
}
|
||||
it.addChild(item)
|
||||
})
|
||||
}
|
||||
|
||||
export class ListItem extends Stack {
|
||||
/**
|
||||
* Set to reuse native view
|
||||
*/
|
||||
@Property
|
||||
identifier?: string
|
||||
}
|
||||
|
||||
export interface IList extends IView {
|
||||
renderItem: (index: number) => ListItem
|
||||
itemCount: number
|
||||
batchCount?: number
|
||||
}
|
||||
|
||||
export class List extends Superview implements IList {
|
||||
private cachedViews: Map<string, ListItem> = new Map
|
||||
private ignoreDirtyCallOnce = false
|
||||
allSubviews() {
|
||||
return this.cachedViews.values()
|
||||
}
|
||||
|
||||
@Property
|
||||
itemCount = 0
|
||||
|
||||
@Property
|
||||
renderItem!: (index: number) => ListItem
|
||||
|
||||
@Property
|
||||
batchCount = 15
|
||||
|
||||
private getItem(itemIdx: number) {
|
||||
let view = this.cachedViews.get(`${itemIdx}`)
|
||||
if (view === undefined) {
|
||||
view = this.renderItem(itemIdx)
|
||||
view.superview = this
|
||||
this.cachedViews.set(`${itemIdx}`, view)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
isDirty() {
|
||||
if (this.ignoreDirtyCallOnce) {
|
||||
this.ignoreDirtyCallOnce = false
|
||||
//Ignore the dirty call once.
|
||||
return false
|
||||
}
|
||||
return super.isDirty()
|
||||
}
|
||||
|
||||
private renderBunchedItems(start: number, length: number) {
|
||||
this.ignoreDirtyCallOnce = true;
|
||||
return new Array(Math.min(length, this.itemCount - start)).fill(0).map((_, idx) => {
|
||||
const listItem = this.getItem(start + idx)
|
||||
return listItem.toModel()
|
||||
})
|
||||
}
|
||||
}
|
@ -14,9 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import './../runtime/global'
|
||||
import { View, Group, Root } from "./view";
|
||||
import { loge, log } from '../util/log';
|
||||
import { View, Group } from "./view";
|
||||
import { loge } from '../util/log';
|
||||
import { Model } from '../util/types';
|
||||
import { Root } from './layout';
|
||||
|
||||
|
||||
export function NativeCall(target: Panel, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
@ -93,13 +94,13 @@ export abstract class Panel {
|
||||
for (let i = 2; i < arguments.length; i++) {
|
||||
argumentsList.push(arguments[i])
|
||||
}
|
||||
Reflect.apply(v.responseCallback, v, argumentsList)
|
||||
return Reflect.apply(v.responseCallback, v, argumentsList)
|
||||
}
|
||||
|
||||
private retrospectView(ids: string[]): View {
|
||||
return ids.reduce((acc: View, cur) => {
|
||||
if (Reflect.has(acc, "subViewById")) {
|
||||
return Reflect.apply(Reflect.get(acc, "subViewById"), acc, [cur])
|
||||
if (Reflect.has(acc, "subviewById")) {
|
||||
return Reflect.apply(Reflect.get(acc, "subviewById"), acc, [cur])
|
||||
}
|
||||
return acc
|
||||
}, this.__root__)
|
||||
@ -112,13 +113,13 @@ export abstract class Panel {
|
||||
}
|
||||
|
||||
private hookBeforeNativeCall() {
|
||||
this.__root__.clean()
|
||||
}
|
||||
|
||||
private hookAfterNativeCall() {
|
||||
if (this.__root__.isDirty()) {
|
||||
const model = this.__root__.toModel()
|
||||
this.nativeRender(model)
|
||||
this.__root__.clean()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,13 +35,36 @@ export interface LayoutConfig {
|
||||
bottom?: number,
|
||||
}
|
||||
alignment?: Gravity
|
||||
//Only affective in VLayout or HLayout
|
||||
weight?: number
|
||||
}
|
||||
|
||||
export function Property(target: Object, propKey: string) {
|
||||
Reflect.defineMetadata(propKey, true, target)
|
||||
}
|
||||
|
||||
export abstract class View implements Modeling {
|
||||
export interface IView {
|
||||
width?: number
|
||||
height?: number
|
||||
bgColor?: Color | GradientColor
|
||||
corners?: number | { leftTop?: number; rightTop?: number; leftBottom?: number; rightBottom?: number }
|
||||
border?: { width: number; color: Color; }
|
||||
shadow?: { color: Color; opacity: number; radius: number; offsetX: number; offsetY: number }
|
||||
alpha?: number
|
||||
hidden?: boolean
|
||||
padding?: {
|
||||
left?: number,
|
||||
right?: number,
|
||||
top?: number,
|
||||
bottom?: number,
|
||||
}
|
||||
layoutConfig?: LayoutConfig
|
||||
onClick?: Function
|
||||
identifier?: string
|
||||
}
|
||||
|
||||
|
||||
export abstract class View implements Modeling, IView {
|
||||
@Property
|
||||
width: number = 0
|
||||
|
||||
@ -89,13 +112,7 @@ export abstract class View implements Modeling {
|
||||
@Property
|
||||
onClick?: Function
|
||||
|
||||
/**
|
||||
* Set to reuse native view
|
||||
*/
|
||||
@Property
|
||||
identifier?: string
|
||||
|
||||
parent?: Group
|
||||
superview?: Superview
|
||||
|
||||
callbacks: Map<String, Function> = new Map
|
||||
|
||||
@ -106,7 +123,10 @@ export abstract class View implements Modeling {
|
||||
}
|
||||
|
||||
private id2Callback(id: string) {
|
||||
const f = this.callbacks.get(id)
|
||||
let f = this.callbacks.get(id)
|
||||
if (f === undefined) {
|
||||
f = Reflect.get(this, id) as Function
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
@ -173,7 +193,11 @@ export abstract class View implements Modeling {
|
||||
}
|
||||
/** Anchor end*/
|
||||
|
||||
__dirty_props__: { [index: string]: Model | undefined } = {}
|
||||
private __dirty_props__: { [index: string]: Model | undefined } = {}
|
||||
|
||||
get dirtyProps() {
|
||||
return this.__dirty_props__
|
||||
}
|
||||
|
||||
nativeViewModel = {
|
||||
id: this.viewId,
|
||||
@ -188,9 +212,6 @@ export abstract class View implements Modeling {
|
||||
newV = obj2Model(newV)
|
||||
}
|
||||
this.__dirty_props__[propKey] = newV
|
||||
if (this.parent instanceof Group) {
|
||||
this.parent.onChildPropertyChanged(this)
|
||||
}
|
||||
}
|
||||
|
||||
clean() {
|
||||
@ -212,7 +233,7 @@ export abstract class View implements Modeling {
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
argumentsList.push(arguments[i])
|
||||
}
|
||||
Reflect.apply(f, this, argumentsList)
|
||||
return Reflect.apply(f, this, argumentsList)
|
||||
} else {
|
||||
loge(`Cannot find callback:${id} for ${JSON.stringify(this.toModel())}`)
|
||||
}
|
||||
@ -221,249 +242,80 @@ export abstract class View implements Modeling {
|
||||
toModel() {
|
||||
return this.nativeViewModel
|
||||
}
|
||||
|
||||
let(block: (it: this) => void) {
|
||||
block(this)
|
||||
}
|
||||
|
||||
also(block: (it: this) => void) {
|
||||
block(this)
|
||||
return this
|
||||
}
|
||||
|
||||
in(group: Group) {
|
||||
group.addChild(this)
|
||||
}
|
||||
}
|
||||
|
||||
export interface StackConfig extends LayoutConfig {
|
||||
|
||||
}
|
||||
|
||||
export interface LinearConfig extends LayoutConfig {
|
||||
weight?: number
|
||||
}
|
||||
|
||||
export interface SuperView {
|
||||
subViewById(id: string): View | undefined
|
||||
}
|
||||
|
||||
export abstract class Group extends View implements SuperView {
|
||||
@Property
|
||||
readonly children: View[] = new Proxy([], {
|
||||
set: (target, index, value) => {
|
||||
if (index === 'length') {
|
||||
this.getDirtyChildrenModel().length = value as number
|
||||
} else if (typeof index === 'string'
|
||||
&& parseInt(index) >= 0
|
||||
&& value instanceof View) {
|
||||
value.parent = this
|
||||
const childrenModel = this.getDirtyChildrenModel()
|
||||
childrenModel[parseInt(index)] = value.nativeViewModel
|
||||
}
|
||||
if (this.parent) {
|
||||
this.parent.onChildPropertyChanged(this)
|
||||
}
|
||||
|
||||
return Reflect.set(target, index, value)
|
||||
}
|
||||
})
|
||||
|
||||
subViewById(id: string): View | undefined {
|
||||
for (let view of this.children) {
|
||||
if (view.viewId === id) {
|
||||
return view
|
||||
export abstract class Superview extends View {
|
||||
subviewById(id: string): View | undefined {
|
||||
for (let v of this.allSubviews()) {
|
||||
if (v.viewId === id) {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
addChild(view: View) {
|
||||
this.children.push(view)
|
||||
abstract allSubviews(): Iterable<View>
|
||||
|
||||
isDirty() {
|
||||
if (super.isDirty()) {
|
||||
return true
|
||||
} else {
|
||||
for (const v of this.allSubviews()) {
|
||||
if (v.isDirty()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
clean() {
|
||||
this.children.forEach(e => { e.clean() })
|
||||
for (let v of this.allSubviews()) {
|
||||
v.clean()
|
||||
}
|
||||
super.clean()
|
||||
}
|
||||
|
||||
getDirtyChildrenModel(): Model[] {
|
||||
if (this.__dirty_props__.children === undefined) {
|
||||
this.__dirty_props__.children = []
|
||||
}
|
||||
return this.__dirty_props__.children as Model[]
|
||||
}
|
||||
|
||||
toModel() {
|
||||
if (this.__dirty_props__.children != undefined) {
|
||||
(this.__dirty_props__.children as Model[]).length = this.children.length
|
||||
const subviews = []
|
||||
for (let v of this.allSubviews()) {
|
||||
if (v.isDirty()) {
|
||||
subviews.push(v.toModel())
|
||||
}
|
||||
}
|
||||
this.dirtyProps.subviews = subviews
|
||||
return super.toModel()
|
||||
}
|
||||
}
|
||||
|
||||
onChildPropertyChanged(child: View) {
|
||||
this.getDirtyChildrenModel()[this.children.indexOf(child)] = child.nativeViewModel
|
||||
this.getDirtyChildrenModel().length = this.children.length
|
||||
if (this.parent) {
|
||||
this.parent.onChildPropertyChanged(this)
|
||||
export abstract class Group extends Superview {
|
||||
|
||||
readonly children: View[] = new Proxy([], {
|
||||
set: (target, index, value) => {
|
||||
const ret = Reflect.set(target, index, value)
|
||||
// Let getDirty return true
|
||||
this.dirtyProps.children = this.children.map(e => e.viewId)
|
||||
return ret
|
||||
}
|
||||
})
|
||||
|
||||
allSubviews() {
|
||||
return this.children
|
||||
}
|
||||
|
||||
isDirty() {
|
||||
return super.isDirty()
|
||||
addChild(view: View) {
|
||||
this.children.push(view)
|
||||
}
|
||||
}
|
||||
|
||||
export class Stack extends Group {
|
||||
@Property
|
||||
gravity?: Gravity
|
||||
}
|
||||
|
||||
export class Scroller extends View implements SuperView {
|
||||
@Property
|
||||
contentView?: View
|
||||
|
||||
subViewById(id: string): View | undefined {
|
||||
return this.contentView
|
||||
}
|
||||
}
|
||||
|
||||
export class Root extends Stack {
|
||||
|
||||
}
|
||||
class LinearLayout extends Group {
|
||||
@Property
|
||||
space?: number
|
||||
|
||||
@Property
|
||||
gravity?: Gravity
|
||||
}
|
||||
|
||||
export class VLayout extends LinearLayout {
|
||||
}
|
||||
|
||||
export class HLayout extends LinearLayout {
|
||||
}
|
||||
|
||||
export class Text extends View {
|
||||
@Property
|
||||
text?: string
|
||||
|
||||
@Property
|
||||
textColor?: Color
|
||||
|
||||
@Property
|
||||
textSize?: number
|
||||
|
||||
@Property
|
||||
maxLines?: number
|
||||
|
||||
@Property
|
||||
textAlignment?: Gravity
|
||||
}
|
||||
|
||||
export class Image extends View {
|
||||
@Property
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
export class List extends View implements SuperView {
|
||||
private cachedViews: Map<string, View> = new Map
|
||||
|
||||
subViewById(id: string): View | undefined {
|
||||
return this.cachedViews.get(id)
|
||||
}
|
||||
|
||||
@Property
|
||||
itemCount = 0
|
||||
|
||||
@Property
|
||||
renderItem!: (index: number) => View
|
||||
|
||||
|
||||
private getItem(itemIdx: number) {
|
||||
let view = this.cachedViews.get(`${itemIdx}`)
|
||||
if (view === undefined) {
|
||||
view = this.renderItem(itemIdx)
|
||||
this.cachedViews.set(`${itemIdx}`, view)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
@Property
|
||||
private renderBunchedItems(items: number[]): View[] {
|
||||
return items.map(e => this.getItem(e))
|
||||
}
|
||||
}
|
||||
|
||||
export class SectionList extends View implements SuperView {
|
||||
private cachedViews: Map<string, View> = new Map
|
||||
|
||||
subViewById(id: string): View | undefined {
|
||||
return this.cachedViews.get(id)
|
||||
}
|
||||
@Property
|
||||
sectionRowsCount: number[] = []
|
||||
|
||||
@Property
|
||||
renderSectionHeader!: (sectionIdx: number) => View
|
||||
|
||||
@Property
|
||||
renderItem!: (sectionIdx: number, itemIdx: number) => View
|
||||
|
||||
@Property
|
||||
sectionHeaderSticky = true
|
||||
|
||||
setupSectionRows(sectionCount: number, numberOfSection: (section: number) => number) {
|
||||
this.sectionRowsCount = [...Array(sectionCount).keys()].map(e => numberOfSection(e))
|
||||
}
|
||||
|
||||
private getItem(sectionIdx: number, itemIdx: number) {
|
||||
let view = this.cachedViews.get(`${sectionIdx}:${itemIdx}`)
|
||||
if (view === undefined) {
|
||||
view = this.renderItem(sectionIdx, itemIdx)
|
||||
this.cachedViews.set(`${sectionIdx}:${itemIdx}`, view)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
private getSectionHeader(sectionIdx: number) {
|
||||
let view = this.cachedViews.get(`${sectionIdx}:`)
|
||||
if (view === undefined) {
|
||||
view = this.renderSectionHeader(sectionIdx)
|
||||
this.cachedViews.set(`${sectionIdx}:`, view)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
@Property
|
||||
private renderBunchedItems(items: Array<{ itemIdx: number, sectionIdx: number }>,
|
||||
headers: number[]): { items: View[], headers: View[] } {
|
||||
return {
|
||||
items: items.map(e => this.getItem(e.sectionIdx, e.itemIdx)),
|
||||
headers: headers.map(e => this.getSectionHeader(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Slide extends View implements SuperView {
|
||||
@Property
|
||||
pageCount = 0
|
||||
|
||||
@Property
|
||||
renderPage!: (pageIdx: number) => View
|
||||
|
||||
private cachedViews: Map<string, View> = new Map
|
||||
subViewById(id: string): View | undefined {
|
||||
return this.cachedViews.get(id)
|
||||
}
|
||||
private getPage(pageIdx: number) {
|
||||
let view = this.cachedViews.get(`${pageIdx}`)
|
||||
if (view === undefined) {
|
||||
view = this.renderPage(pageIdx)
|
||||
this.cachedViews.set(`${pageIdx}`, view)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
@Property
|
||||
private renderBunchedPages(pages: number[]): View[] {
|
||||
return pages.map(e => this.getPage(e))
|
||||
}
|
||||
}
|
52
js-framework/src/ui/widgets.ts
Normal file
52
js-framework/src/ui/widgets.ts
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright [2019] [Doric.Pub]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { IView, View, Property } from "./view"
|
||||
import { Color } from "../util/color"
|
||||
import { Gravity } from "../util/gravity"
|
||||
|
||||
export interface IText extends IView {
|
||||
text?: string
|
||||
textColor?: Color
|
||||
textSize?: number
|
||||
maxLines?: number
|
||||
textAlignment?: Gravity
|
||||
}
|
||||
|
||||
export class Text extends View implements IText {
|
||||
@Property
|
||||
text?: string
|
||||
|
||||
@Property
|
||||
textColor?: Color
|
||||
|
||||
@Property
|
||||
textSize?: number
|
||||
|
||||
@Property
|
||||
maxLines?: number
|
||||
|
||||
@Property
|
||||
textAlignment?: Gravity
|
||||
}
|
||||
|
||||
export interface IImage extends IView {
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
export class Image extends View implements IImage {
|
||||
@Property
|
||||
imageUrl?: string
|
||||
}
|
@ -77,3 +77,6 @@ export class Gravity implements Modeling {
|
||||
}
|
||||
|
||||
}
|
||||
export function gravity() {
|
||||
return new Gravity
|
||||
}
|
@ -35,7 +35,7 @@ export function obj2Model(obj: Model): Model {
|
||||
}
|
||||
}
|
||||
|
||||
type _M = string | number | boolean | Modeling | { [index: string]: Model | undefined }
|
||||
type _M = string | number | boolean | Modeling | { [index: string]: Model } | undefined
|
||||
export type Model = _M | Array<_M>
|
||||
|
||||
export type Binder<T> = (v: T) => void
|
||||
|
Reference in New Issue
Block a user