Merge branch 'feature/dev' into 'master'
Feature/dev See merge request !28
This commit is contained in:
commit
458fe2385f
@ -27,6 +27,7 @@ dependencies {
|
||||
implementation 'com.github.bumptech.glide:glide:4.10.0'
|
||||
implementation 'com.github.bumptech.glide:annotations:4.10.0'
|
||||
implementation 'com.github.penfeizhou.android.animation:glide-plugin:1.3.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-4'
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
package="pub.doric.demo">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name=".MyApplication"
|
||||
android:allowBackup="true"
|
||||
@ -12,6 +13,7 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".PullableActivity"></activity>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
@ -16,6 +16,7 @@
|
||||
package pub.doric.demo;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
@ -34,6 +35,7 @@ import java.util.List;
|
||||
|
||||
import pub.doric.DoricActivity;
|
||||
import pub.doric.devkit.ui.DemoDebugActivity;
|
||||
import pub.doric.refresh.DoricSwipeLayout;
|
||||
import pub.doric.utils.DoricUtils;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
@ -43,11 +45,25 @@ public class MainActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
final DoricSwipeLayout swipeLayout = findViewById(R.id.swipe_layout);
|
||||
swipeLayout.setOnRefreshListener(new DoricSwipeLayout.OnRefreshListener() {
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
swipeLayout.setRefreshing(false);
|
||||
}
|
||||
});
|
||||
swipeLayout.setBackgroundColor(Color.YELLOW);
|
||||
swipeLayout.getRefreshView().setBackgroundColor(Color.RED);
|
||||
TextView textView = new TextView(this);
|
||||
textView.setText("This is header");
|
||||
swipeLayout.getRefreshView().setContent(textView);
|
||||
RecyclerView recyclerView = findViewById(R.id.root);
|
||||
recyclerView.setBackgroundColor(Color.WHITE);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
try {
|
||||
String[] demos = getAssets().list("demo");
|
||||
List<String> ret = new ArrayList<>();
|
||||
ret.add("Test");
|
||||
for (String str : demos) {
|
||||
if (str.endsWith("js")) {
|
||||
ret.add(str);
|
||||
@ -91,6 +107,11 @@ public class MainActivity extends AppCompatActivity {
|
||||
tv.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (data[position].contains("Test")) {
|
||||
Intent intent = new Intent(tv.getContext(), PullableActivity.class);
|
||||
tv.getContext().startActivity(intent);
|
||||
return;
|
||||
}
|
||||
if (data[position].contains("NavigatorDemo")) {
|
||||
Intent intent = new Intent(tv.getContext(), DoricActivity.class);
|
||||
intent.putExtra("scheme", "assets://demo/" + data[position]);
|
||||
|
@ -0,0 +1,28 @@
|
||||
package pub.doric.demo;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import pub.doric.refresh.DoricSwipeLayout;
|
||||
|
||||
public class PullableActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_pullable);
|
||||
final DoricSwipeLayout swipeRefreshLayout = findViewById(R.id.swipe_layout);
|
||||
FrameLayout frameLayout = new FrameLayout(this);
|
||||
frameLayout.setBackgroundColor(Color.YELLOW);
|
||||
swipeRefreshLayout.addView(frameLayout);
|
||||
swipeRefreshLayout.setOnRefreshListener(new DoricSwipeLayout.OnRefreshListener() {
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,8 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<pub.doric.refresh.DoricSwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/swipe_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity" />
|
||||
</pub.doric.refresh.DoricSwipeLayout>
|
13
Android/app/src/main/res/layout/activity_pullable.xml
Normal file
13
Android/app/src/main/res/layout/activity_pullable.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".PullableActivity">
|
||||
|
||||
<pub.doric.refresh.DoricSwipeLayout
|
||||
android:id="@+id/swipe_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</FrameLayout>
|
@ -25,6 +25,7 @@ import pub.doric.plugin.NavigatorPlugin;
|
||||
import pub.doric.plugin.NetworkPlugin;
|
||||
import pub.doric.plugin.ShaderPlugin;
|
||||
import pub.doric.plugin.StoragePlugin;
|
||||
import pub.doric.refresh.RefreshableNode;
|
||||
import pub.doric.shader.HLayoutNode;
|
||||
import pub.doric.shader.ImageNode;
|
||||
import pub.doric.shader.ScrollerNode;
|
||||
@ -96,6 +97,7 @@ public class DoricRegistry {
|
||||
this.registerViewNode(ScrollerNode.class);
|
||||
this.registerViewNode(SliderNode.class);
|
||||
this.registerViewNode(SlideItemNode.class);
|
||||
this.registerViewNode(RefreshableNode.class);
|
||||
initRegistry(this);
|
||||
}
|
||||
|
||||
|
@ -96,6 +96,12 @@ public class DoricJSEngine implements Handler.Callback, DoricTimerExtension.Time
|
||||
return null;
|
||||
}
|
||||
});
|
||||
mDoricJSE.injectGlobalJSFunction(DoricConstant.INJECT_EMPTY, new JavaFunction() {
|
||||
@Override
|
||||
public JavaValue exec(JSDecoder[] args) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
mDoricJSE.injectGlobalJSFunction(DoricConstant.INJECT_REQUIRE, new JavaFunction() {
|
||||
@Override
|
||||
public JavaValue exec(JSDecoder[] args) {
|
||||
|
@ -0,0 +1,100 @@
|
||||
package pub.doric.refresh;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @Description: pub.doric.pullable
|
||||
* @Author: pengfei.zhou
|
||||
* @CreateDate: 2019-11-25
|
||||
*/
|
||||
public class DoricRefreshView extends FrameLayout implements PullingListener {
|
||||
private View content;
|
||||
private Animation.AnimationListener mListener;
|
||||
|
||||
private PullingListener mPullingListenr;
|
||||
|
||||
public DoricRefreshView(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public DoricRefreshView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public DoricRefreshView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setContent(View v) {
|
||||
removeAllViews();
|
||||
content = v;
|
||||
if (v.getLayoutParams() instanceof FrameLayout.LayoutParams) {
|
||||
((LayoutParams) v.getLayoutParams()).gravity = Gravity.BOTTOM;
|
||||
} else {
|
||||
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.gravity = Gravity.CENTER;
|
||||
v.setLayoutParams(params);
|
||||
}
|
||||
addView(v);
|
||||
}
|
||||
|
||||
public View getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
public void setPullingListenr(PullingListener listenr) {
|
||||
this.mPullingListenr = listenr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startAnimation() {
|
||||
if (mPullingListenr != null) {
|
||||
mPullingListenr.startAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopAnimation() {
|
||||
if (mPullingListenr != null) {
|
||||
mPullingListenr.stopAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgressRotation(float rotation) {
|
||||
if (mPullingListenr != null) {
|
||||
mPullingListenr.setProgressRotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
public void setAnimationListener(Animation.AnimationListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationStart() {
|
||||
super.onAnimationStart();
|
||||
if (mListener != null) {
|
||||
mListener.onAnimationStart(getAnimation());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEnd() {
|
||||
super.onAnimationEnd();
|
||||
if (mListener != null) {
|
||||
mListener.onAnimationEnd(getAnimation());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,958 @@
|
||||
package pub.doric.refresh;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Transformation;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.view.NestedScrollingChild;
|
||||
import androidx.core.view.NestedScrollingChildHelper;
|
||||
import androidx.core.view.NestedScrollingParent;
|
||||
import androidx.core.view.NestedScrollingParentHelper;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.widget.ListViewCompat;
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable;
|
||||
|
||||
import android.view.animation.Animation.AnimationListener;
|
||||
|
||||
/**
|
||||
* @Description: pub.doric.pullable
|
||||
* @Author: pengfei.zhou
|
||||
* @CreateDate: 2019-11-25
|
||||
*/
|
||||
public class DoricSwipeLayout extends ViewGroup implements NestedScrollingParent,
|
||||
NestedScrollingChild {
|
||||
// Maps to ProgressBar.Large style
|
||||
public static final int LARGE = CircularProgressDrawable.LARGE;
|
||||
// Maps to ProgressBar default style
|
||||
public static final int DEFAULT = CircularProgressDrawable.DEFAULT;
|
||||
|
||||
public static final int DEFAULT_SLINGSHOT_DISTANCE = -1;
|
||||
|
||||
@VisibleForTesting
|
||||
static final int CIRCLE_DIAMETER = 40;
|
||||
@VisibleForTesting
|
||||
static final int CIRCLE_DIAMETER_LARGE = 56;
|
||||
|
||||
private static final String LOG_TAG = DoricSwipeLayout.class.getSimpleName();
|
||||
|
||||
private static final int MAX_ALPHA = 255;
|
||||
private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA);
|
||||
|
||||
private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
|
||||
private static final int INVALID_POINTER = -1;
|
||||
private static final float DRAG_RATE = .5f;
|
||||
|
||||
// Max amount of circle that can be filled by progress during swipe gesture,
|
||||
// where 1.0 is a full circle
|
||||
private static final float MAX_PROGRESS_ANGLE = .8f;
|
||||
|
||||
private static final int SCALE_DOWN_DURATION = 150;
|
||||
|
||||
private static final int ALPHA_ANIMATION_DURATION = 300;
|
||||
|
||||
private static final int ANIMATE_TO_TRIGGER_DURATION = 200;
|
||||
|
||||
private static final int ANIMATE_TO_START_DURATION = 200;
|
||||
|
||||
// Default offset in dips from the top of the view to where the progress spinner should stop
|
||||
private static final int DEFAULT_CIRCLE_TARGET = 64;
|
||||
|
||||
private View mTarget; // the target of the gesture
|
||||
OnRefreshListener mListener;
|
||||
boolean mRefreshing = false;
|
||||
private int mTouchSlop;
|
||||
private float mTotalDragDistance = -1;
|
||||
|
||||
// If nested scrolling is enabled, the total amount that needed to be
|
||||
// consumed by this as the nested scrolling parent is used in place of the
|
||||
// overscroll determined by MOVE events in the onTouch handler
|
||||
private float mTotalUnconsumed;
|
||||
private final NestedScrollingParentHelper mNestedScrollingParentHelper;
|
||||
private final NestedScrollingChildHelper mNestedScrollingChildHelper;
|
||||
private final int[] mParentScrollConsumed = new int[2];
|
||||
private final int[] mParentOffsetInWindow = new int[2];
|
||||
private boolean mNestedScrollInProgress;
|
||||
|
||||
private int mMediumAnimationDuration;
|
||||
int mCurrentTargetOffsetTop;
|
||||
|
||||
private float mInitialMotionY;
|
||||
private float mInitialDownY;
|
||||
private boolean mIsBeingDragged;
|
||||
private int mActivePointerId = INVALID_POINTER;
|
||||
|
||||
// Target is returning to its start offset because it was cancelled or a
|
||||
// refresh was triggered.
|
||||
private boolean mReturningToStart;
|
||||
private final DecelerateInterpolator mDecelerateInterpolator;
|
||||
private static final int[] LAYOUT_ATTRS = new int[]{
|
||||
android.R.attr.enabled
|
||||
};
|
||||
|
||||
private int mCircleViewIndex = -1;
|
||||
|
||||
protected int mFrom;
|
||||
|
||||
float mStartingScale;
|
||||
|
||||
protected int mOriginalOffsetTop;
|
||||
|
||||
int mSpinnerOffsetEnd;
|
||||
|
||||
int mCustomSlingshotDistance;
|
||||
|
||||
private Animation mScaleAnimation;
|
||||
|
||||
private Animation mScaleDownAnimation;
|
||||
|
||||
private Animation mScaleDownToStartAnimation;
|
||||
|
||||
boolean mNotify;
|
||||
|
||||
// Whether the client has set a custom starting position;
|
||||
boolean mUsingCustomStart;
|
||||
|
||||
private OnChildScrollUpCallback mChildScrollUpCallback;
|
||||
|
||||
private DoricRefreshView mRefreshView;
|
||||
private AnimationListener mRefreshListener = new AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
if (mRefreshing) {
|
||||
mRefreshView.startAnimation();
|
||||
if (mNotify) {
|
||||
if (mListener != null) {
|
||||
mListener.onRefresh();
|
||||
}
|
||||
}
|
||||
mCurrentTargetOffsetTop = mRefreshView.getTop();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
private int mPullDownHeight = 0;
|
||||
private ValueAnimator headerViewAnimator;
|
||||
|
||||
void reset() {
|
||||
mRefreshing = false;
|
||||
if (headerViewAnimator != null && headerViewAnimator.isRunning()) {
|
||||
headerViewAnimator.cancel();
|
||||
}
|
||||
headerViewAnimator = ValueAnimator.ofInt(mRefreshView.getBottom(), 0);
|
||||
headerViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
mCurrentTargetOffsetTop = (int) animation.getAnimatedValue()
|
||||
- mRefreshView.getMeasuredHeight();
|
||||
mRefreshView.requestLayout();
|
||||
}
|
||||
});
|
||||
headerViewAnimator.addListener(new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mRefreshView.stopAnimation();
|
||||
mRefreshView.setVisibility(View.GONE);
|
||||
// Return the circle to its start position
|
||||
|
||||
setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop);
|
||||
mCurrentTargetOffsetTop = mRefreshView.getTop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) {
|
||||
|
||||
}
|
||||
});
|
||||
headerViewAnimator.setDuration(SCALE_DOWN_DURATION);
|
||||
headerViewAnimator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
if (!enabled) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple constructor to use when creating a SwipeRefreshLayout from code.
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
public DoricSwipeLayout(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that is called when inflating SwipeRefreshLayout from XML.
|
||||
*
|
||||
* @param context
|
||||
* @param attrs
|
||||
*/
|
||||
public DoricSwipeLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
|
||||
mMediumAnimationDuration = getResources().getInteger(
|
||||
android.R.integer.config_mediumAnimTime);
|
||||
|
||||
setWillNotDraw(false);
|
||||
mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
|
||||
|
||||
final DisplayMetrics metrics = getResources().getDisplayMetrics();
|
||||
|
||||
createProgressView();
|
||||
setChildrenDrawingOrderEnabled(true);
|
||||
// the absolute offset has to take into account that the circle starts at an offset
|
||||
mSpinnerOffsetEnd = (int) (DEFAULT_CIRCLE_TARGET * metrics.density);
|
||||
mTotalDragDistance = mSpinnerOffsetEnd;
|
||||
mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
|
||||
|
||||
mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
|
||||
setNestedScrollingEnabled(true);
|
||||
|
||||
moveToStart(1.0f);
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
|
||||
setEnabled(a.getBoolean(0, true));
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
public void setPullDownHeight(int height) {
|
||||
mPullDownHeight = height;
|
||||
mOriginalOffsetTop = mCurrentTargetOffsetTop = -height;
|
||||
mSpinnerOffsetEnd = height;
|
||||
mTotalDragDistance = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getChildDrawingOrder(int childCount, int i) {
|
||||
if (mCircleViewIndex < 0) {
|
||||
return i;
|
||||
} else if (i == childCount - 1) {
|
||||
// Draw the selected child last
|
||||
return mCircleViewIndex;
|
||||
} else if (i >= mCircleViewIndex) {
|
||||
// Move the children after the selected child earlier one
|
||||
return i + 1;
|
||||
} else {
|
||||
// Keep the children before the selected child the same
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
private void createProgressView() {
|
||||
mRefreshView = new DoricRefreshView(getContext());
|
||||
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
|
||||
addView(mRefreshView, layoutParams);
|
||||
}
|
||||
|
||||
public DoricRefreshView getRefreshView() {
|
||||
return mRefreshView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the listener to be notified when a refresh is triggered via the swipe
|
||||
* gesture.
|
||||
*/
|
||||
public void setOnRefreshListener(@Nullable OnRefreshListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the widget that refresh state has changed. Do not call this when
|
||||
* refresh is triggered by a swipe gesture.
|
||||
*
|
||||
* @param refreshing Whether or not the view should show refresh progress.
|
||||
*/
|
||||
public void setRefreshing(boolean refreshing) {
|
||||
if (refreshing && mRefreshing != refreshing) {
|
||||
// scale and show
|
||||
mRefreshing = refreshing;
|
||||
int endTarget = 0;
|
||||
if (!mUsingCustomStart) {
|
||||
endTarget = mSpinnerOffsetEnd + mOriginalOffsetTop;
|
||||
} else {
|
||||
endTarget = mSpinnerOffsetEnd;
|
||||
}
|
||||
setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop);
|
||||
mNotify = false;
|
||||
startScaleUpAnimation(mRefreshListener);
|
||||
} else {
|
||||
setRefreshing(refreshing, false /* notify */);
|
||||
}
|
||||
}
|
||||
|
||||
private void startScaleUpAnimation(AnimationListener listener) {
|
||||
mRefreshView.setVisibility(View.VISIBLE);
|
||||
mScaleAnimation = new Animation() {
|
||||
@Override
|
||||
public void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
setAnimationProgress(interpolatedTime);
|
||||
}
|
||||
};
|
||||
mScaleAnimation.setDuration(mMediumAnimationDuration);
|
||||
if (listener != null) {
|
||||
mRefreshView.setAnimationListener(listener);
|
||||
}
|
||||
mRefreshView.clearAnimation();
|
||||
mRefreshView.startAnimation(mScaleAnimation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre API 11, this does an alpha animation.
|
||||
*
|
||||
* @param progress
|
||||
*/
|
||||
void setAnimationProgress(float progress) {
|
||||
|
||||
}
|
||||
|
||||
private void setRefreshing(boolean refreshing, final boolean notify) {
|
||||
if (mRefreshing != refreshing) {
|
||||
mNotify = notify;
|
||||
ensureTarget();
|
||||
mRefreshing = refreshing;
|
||||
if (mRefreshing) {
|
||||
animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);
|
||||
} else {
|
||||
startScaleDownAnimation(mRefreshListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void startScaleDownAnimation(Animation.AnimationListener listener) {
|
||||
mScaleDownAnimation = new Animation() {
|
||||
@Override
|
||||
public void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
setAnimationProgress(1 - interpolatedTime);
|
||||
}
|
||||
};
|
||||
mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION);
|
||||
mRefreshView.setAnimationListener(listener);
|
||||
mRefreshView.clearAnimation();
|
||||
mRefreshView.startAnimation(mScaleDownAnimation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the SwipeRefreshWidget is actively showing refresh
|
||||
* progress.
|
||||
*/
|
||||
public boolean isRefreshing() {
|
||||
return mRefreshing;
|
||||
}
|
||||
|
||||
private void ensureTarget() {
|
||||
// Don't bother getting the parent height if the parent hasn't been laid
|
||||
// out yet.
|
||||
if (mTarget == null) {
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View child = getChildAt(i);
|
||||
if (!child.equals(mRefreshView)) {
|
||||
mTarget = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
final int width = getMeasuredWidth();
|
||||
final int height = getMeasuredHeight();
|
||||
if (getChildCount() == 0) {
|
||||
return;
|
||||
}
|
||||
if (mTarget == null) {
|
||||
ensureTarget();
|
||||
}
|
||||
if (mTarget == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int circleWidth = mRefreshView.getMeasuredWidth();
|
||||
int circleHeight = mRefreshView.getMeasuredHeight();
|
||||
|
||||
mRefreshView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop,
|
||||
(width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight);
|
||||
|
||||
final View child = mTarget;
|
||||
final int childLeft = getPaddingLeft();
|
||||
final int childTop = getPaddingTop() + mRefreshView.getBottom();
|
||||
final int childWidth = width - getPaddingLeft() - getPaddingRight();
|
||||
final int childHeight = height - getPaddingTop() - getPaddingBottom();
|
||||
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if (mTarget == null) {
|
||||
ensureTarget();
|
||||
}
|
||||
if (mTarget == null) {
|
||||
return;
|
||||
}
|
||||
mTarget.measure(MeasureSpec.makeMeasureSpec(
|
||||
getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
|
||||
MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
|
||||
getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));
|
||||
mRefreshView.measure(
|
||||
MeasureSpec.makeMeasureSpec(
|
||||
getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
|
||||
MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(
|
||||
(getMeasuredHeight() - getPaddingTop() - getPaddingBottom()) / 3,
|
||||
MeasureSpec.UNSPECIFIED));
|
||||
if (mPullDownHeight != mRefreshView.getMeasuredHeight()) {
|
||||
setPullDownHeight(mRefreshView.getMeasuredHeight());
|
||||
}
|
||||
mCircleViewIndex = -1;
|
||||
// Get the index of the circleview.
|
||||
for (int index = 0; index < getChildCount(); index++) {
|
||||
if (getChildAt(index) == mRefreshView) {
|
||||
mCircleViewIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether it is possible for the child view of this layout to
|
||||
* scroll up. Override this if the child view is a custom view.
|
||||
*/
|
||||
public boolean canChildScrollUp() {
|
||||
if (mChildScrollUpCallback != null) {
|
||||
return mChildScrollUpCallback.canChildScrollUp(this, mTarget);
|
||||
}
|
||||
if (mTarget instanceof ListView) {
|
||||
return ListViewCompat.canScrollList((ListView) mTarget, -1);
|
||||
}
|
||||
return mTarget.canScrollVertically(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback to override {@link androidx.swiperefreshlayout.widget.SwipeRefreshLayout#canChildScrollUp()} method. Non-null
|
||||
* callback will return the value provided by the callback and ignore all internal logic.
|
||||
*
|
||||
* @param callback Callback that should be called when canChildScrollUp() is called.
|
||||
*/
|
||||
public void setOnChildScrollUpCallback(@Nullable OnChildScrollUpCallback callback) {
|
||||
mChildScrollUpCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
ensureTarget();
|
||||
|
||||
final int action = ev.getActionMasked();
|
||||
int pointerIndex;
|
||||
|
||||
if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
|
||||
mReturningToStart = false;
|
||||
}
|
||||
|
||||
if (!isEnabled() || mReturningToStart || canChildScrollUp()
|
||||
|| mRefreshing || mNestedScrollInProgress) {
|
||||
// Fail fast if we're not in a state where a swipe is possible
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
setTargetOffsetTopAndBottom(mOriginalOffsetTop - mRefreshView.getTop());
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
mIsBeingDragged = false;
|
||||
|
||||
pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (pointerIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
mInitialDownY = ev.getY(pointerIndex);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (mActivePointerId == INVALID_POINTER) {
|
||||
Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
|
||||
return false;
|
||||
}
|
||||
|
||||
pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (pointerIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
final float y = ev.getY(pointerIndex);
|
||||
startDragging(y);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onSecondaryPointerUp(ev);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mIsBeingDragged = false;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
break;
|
||||
}
|
||||
|
||||
return mIsBeingDragged;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestDisallowInterceptTouchEvent(boolean b) {
|
||||
// if this is a List < L or another view that doesn't support nested
|
||||
// scrolling, ignore this request so that the vertical scroll event
|
||||
// isn't stolen
|
||||
if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView)
|
||||
|| (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) {
|
||||
// Nope.
|
||||
} else {
|
||||
super.requestDisallowInterceptTouchEvent(b);
|
||||
}
|
||||
}
|
||||
|
||||
// NestedScrollingParent
|
||||
|
||||
@Override
|
||||
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
|
||||
return isEnabled() && !mReturningToStart && !mRefreshing
|
||||
&& (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNestedScrollAccepted(View child, View target, int axes) {
|
||||
// Reset the counter of how much leftover scroll needs to be consumed.
|
||||
mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
|
||||
// Dispatch up to the nested parent
|
||||
startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
|
||||
mTotalUnconsumed = 0;
|
||||
mNestedScrollInProgress = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
|
||||
// If we are in the middle of consuming, a scroll, then we want to move the spinner back up
|
||||
// before allowing the list to scroll
|
||||
if (dy > 0 && mTotalUnconsumed > 0) {
|
||||
if (dy > mTotalUnconsumed) {
|
||||
consumed[1] = dy - (int) mTotalUnconsumed;
|
||||
mTotalUnconsumed = 0;
|
||||
} else {
|
||||
if (dy > 3) {
|
||||
mTotalUnconsumed -= dy;
|
||||
consumed[1] = dy;
|
||||
}
|
||||
}
|
||||
moveSpinner(mTotalUnconsumed);
|
||||
}
|
||||
|
||||
// If a client layout is using a custom start position for the circle
|
||||
// view, they mean to hide it again before scrolling the child view
|
||||
// If we get back to mTotalUnconsumed == 0 and there is more to go, hide
|
||||
// the circle so it isn't exposed if its blocking content is moved
|
||||
if (mUsingCustomStart && dy > 0 && mTotalUnconsumed == 0
|
||||
&& Math.abs(dy - consumed[1]) > 0) {
|
||||
mRefreshView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Now let our nested parent consume the leftovers
|
||||
final int[] parentConsumed = mParentScrollConsumed;
|
||||
if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) {
|
||||
consumed[0] += parentConsumed[0];
|
||||
consumed[1] += parentConsumed[1];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNestedScrollAxes() {
|
||||
return mNestedScrollingParentHelper.getNestedScrollAxes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopNestedScroll(View target) {
|
||||
mNestedScrollingParentHelper.onStopNestedScroll(target);
|
||||
mNestedScrollInProgress = false;
|
||||
// Finish the spinner for nested scrolling if we ever consumed any
|
||||
// unconsumed nested scroll
|
||||
if (mTotalUnconsumed > 0) {
|
||||
finishSpinner(mTotalUnconsumed);
|
||||
mTotalUnconsumed = 0;
|
||||
}
|
||||
// Dispatch up our nested parent
|
||||
stopNestedScroll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed,
|
||||
final int dxUnconsumed, final int dyUnconsumed) {
|
||||
// Dispatch up to the nested parent first
|
||||
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
|
||||
mParentOffsetInWindow);
|
||||
|
||||
// This is a bit of a hack. Nested scrolling works from the bottom up, and as we are
|
||||
// sometimes between two nested scrolling views, we need a way to be able to know when any
|
||||
// nested scrolling parent has stopped handling events. We do that by using the
|
||||
// 'offset in window 'functionality to see if we have been moved from the event.
|
||||
// This is a decent indication of whether we should take over the event stream or not.
|
||||
final int dy = dyUnconsumed + mParentOffsetInWindow[1];
|
||||
if (dy < 0 && !canChildScrollUp()) {
|
||||
mTotalUnconsumed += Math.abs(dy);
|
||||
moveSpinner(mTotalUnconsumed);
|
||||
}
|
||||
}
|
||||
|
||||
// NestedScrollingChild
|
||||
|
||||
@Override
|
||||
public void setNestedScrollingEnabled(boolean enabled) {
|
||||
mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNestedScrollingEnabled() {
|
||||
return mNestedScrollingChildHelper.isNestedScrollingEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startNestedScroll(int axes) {
|
||||
return mNestedScrollingChildHelper.startNestedScroll(axes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopNestedScroll() {
|
||||
mNestedScrollingChildHelper.stopNestedScroll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNestedScrollingParent() {
|
||||
return mNestedScrollingChildHelper.hasNestedScrollingParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
|
||||
int dyUnconsumed, int[] offsetInWindow) {
|
||||
return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
|
||||
dxUnconsumed, dyUnconsumed, offsetInWindow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
|
||||
return mNestedScrollingChildHelper.dispatchNestedPreScroll(
|
||||
dx, dy, consumed, offsetInWindow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNestedPreFling(View target, float velocityX,
|
||||
float velocityY) {
|
||||
return dispatchNestedPreFling(velocityX, velocityY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNestedFling(View target, float velocityX, float velocityY,
|
||||
boolean consumed) {
|
||||
return dispatchNestedFling(velocityX, velocityY, consumed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
|
||||
return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
|
||||
return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
|
||||
}
|
||||
|
||||
private boolean isAnimationRunning(Animation animation) {
|
||||
return animation != null && animation.hasStarted() && !animation.hasEnded();
|
||||
}
|
||||
|
||||
private void moveSpinner(float overscrollTop) {
|
||||
float originalDragPercent = overscrollTop / mTotalDragDistance;
|
||||
|
||||
float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
|
||||
float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;
|
||||
float slingshotDist = mCustomSlingshotDistance > 0
|
||||
? mCustomSlingshotDistance
|
||||
: (mUsingCustomStart
|
||||
? mSpinnerOffsetEnd - mOriginalOffsetTop
|
||||
: mSpinnerOffsetEnd);
|
||||
float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2)
|
||||
/ slingshotDist);
|
||||
float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
|
||||
(tensionSlingshotPercent / 4), 2)) * 2f;
|
||||
float extraMove = (slingshotDist) * tensionPercent * 2;
|
||||
|
||||
int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove);
|
||||
// where 1.0f is a full circle
|
||||
if (mRefreshView.getVisibility() != View.VISIBLE) {
|
||||
mRefreshView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
mRefreshView.setScaleX(1f);
|
||||
mRefreshView.setScaleY(1f);
|
||||
|
||||
setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop);
|
||||
}
|
||||
|
||||
private void finishSpinner(float overscrollTop) {
|
||||
if (overscrollTop > mTotalDragDistance) {
|
||||
setRefreshing(true, true /* notify */);
|
||||
} else {
|
||||
// cancel refresh
|
||||
mRefreshing = false;
|
||||
Animation.AnimationListener listener = null;
|
||||
listener = new Animation.AnimationListener() {
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
startScaleDownAnimation(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
}
|
||||
|
||||
};
|
||||
animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
final int action = ev.getActionMasked();
|
||||
int pointerIndex = -1;
|
||||
|
||||
if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
|
||||
mReturningToStart = false;
|
||||
}
|
||||
|
||||
if (!isEnabled() || mReturningToStart || canChildScrollUp()
|
||||
|| mRefreshing || mNestedScrollInProgress) {
|
||||
// Fail fast if we're not in a state where a swipe is possible
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
mIsBeingDragged = false;
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (pointerIndex < 0) {
|
||||
Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
|
||||
return false;
|
||||
}
|
||||
|
||||
final float y = ev.getY(pointerIndex);
|
||||
startDragging(y);
|
||||
|
||||
if (mIsBeingDragged) {
|
||||
final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
|
||||
if (overscrollTop > 0) {
|
||||
moveSpinner(overscrollTop);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||
pointerIndex = ev.getActionIndex();
|
||||
if (pointerIndex < 0) {
|
||||
Log.e(LOG_TAG,
|
||||
"Got ACTION_POINTER_DOWN event but have an invalid action index.");
|
||||
return false;
|
||||
}
|
||||
mActivePointerId = ev.getPointerId(pointerIndex);
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onSecondaryPointerUp(ev);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP: {
|
||||
pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (pointerIndex < 0) {
|
||||
Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mIsBeingDragged) {
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
|
||||
mIsBeingDragged = false;
|
||||
finishSpinner(overscrollTop);
|
||||
}
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
return false;
|
||||
}
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void startDragging(float y) {
|
||||
final float yDiff = y - mInitialDownY;
|
||||
if (yDiff > mTouchSlop && !mIsBeingDragged) {
|
||||
mInitialMotionY = mInitialDownY + mTouchSlop;
|
||||
mIsBeingDragged = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void animateOffsetToCorrectPosition(int from, AnimationListener listener) {
|
||||
mFrom = from;
|
||||
mAnimateToCorrectPosition.reset();
|
||||
mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION);
|
||||
mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);
|
||||
if (listener != null) {
|
||||
mRefreshView.setAnimationListener(listener);
|
||||
}
|
||||
mRefreshView.clearAnimation();
|
||||
mRefreshView.startAnimation(mAnimateToCorrectPosition);
|
||||
}
|
||||
|
||||
private void animateOffsetToStartPosition(int from, AnimationListener listener) {
|
||||
mFrom = from;
|
||||
mAnimateToStartPosition.reset();
|
||||
mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION);
|
||||
mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
|
||||
if (listener != null) {
|
||||
mRefreshView.setAnimationListener(listener);
|
||||
}
|
||||
mRefreshView.clearAnimation();
|
||||
mRefreshView.startAnimation(mAnimateToStartPosition);
|
||||
}
|
||||
|
||||
private final Animation mAnimateToCorrectPosition = new Animation() {
|
||||
@Override
|
||||
public void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
int targetTop = 0;
|
||||
int endTarget = 0;
|
||||
if (!mUsingCustomStart) {
|
||||
endTarget = mSpinnerOffsetEnd - Math.abs(mOriginalOffsetTop);
|
||||
} else {
|
||||
endTarget = mSpinnerOffsetEnd;
|
||||
}
|
||||
targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
|
||||
int offset = targetTop - mRefreshView.getTop();
|
||||
setTargetOffsetTopAndBottom(offset);
|
||||
}
|
||||
};
|
||||
|
||||
void moveToStart(float interpolatedTime) {
|
||||
int targetTop = 0;
|
||||
targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime));
|
||||
int offset = targetTop - mRefreshView.getTop();
|
||||
setTargetOffsetTopAndBottom(offset);
|
||||
}
|
||||
|
||||
private final Animation mAnimateToStartPosition = new Animation() {
|
||||
@Override
|
||||
public void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
moveToStart(interpolatedTime);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void setTargetOffsetTopAndBottom(int offset) {
|
||||
mRefreshView.bringToFront();
|
||||
ViewCompat.offsetTopAndBottom(mRefreshView, offset);
|
||||
mCurrentTargetOffsetTop = mRefreshView.getTop();
|
||||
if (mRefreshView.getMeasuredHeight() > 0) {
|
||||
mRefreshView.setProgressRotation((float) mRefreshView.getBottom() / (float) mRefreshView.getMeasuredHeight());
|
||||
}
|
||||
}
|
||||
|
||||
private void onSecondaryPointerUp(MotionEvent ev) {
|
||||
final int pointerIndex = ev.getActionIndex();
|
||||
final int pointerId = ev.getPointerId(pointerIndex);
|
||||
if (pointerId == mActivePointerId) {
|
||||
// This was our active pointer going up. Choose a new
|
||||
// active pointer and adjust accordingly.
|
||||
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
|
||||
mActivePointerId = ev.getPointerId(newPointerIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes that wish to be notified when the swipe gesture correctly
|
||||
* triggers a refresh should implement this interface.
|
||||
*/
|
||||
public interface OnRefreshListener {
|
||||
/**
|
||||
* Called when a swipe gesture triggers a refresh.
|
||||
*/
|
||||
void onRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes that wish to override {@link androidx.swiperefreshlayout.widget.SwipeRefreshLayout#canChildScrollUp()} method
|
||||
* behavior should implement this interface.
|
||||
*/
|
||||
public interface OnChildScrollUpCallback {
|
||||
/**
|
||||
* Callback that will be called when {@link androidx.swiperefreshlayout.widget.SwipeRefreshLayout#canChildScrollUp()} method
|
||||
* is called to allow the implementer to override its behavior.
|
||||
*
|
||||
* @param parent SwipeRefreshLayout that this callback is overriding.
|
||||
* @param child The child view of SwipeRefreshLayout.
|
||||
* @return Whether it is possible for the child view of parent layout to scroll up.
|
||||
*/
|
||||
boolean canChildScrollUp(@NonNull DoricSwipeLayout parent, @Nullable View child);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package pub.doric.refresh;
|
||||
|
||||
/**
|
||||
* @Description: pub.doric.pullable
|
||||
* @Author: pengfei.zhou
|
||||
* @CreateDate: 2019-11-25
|
||||
*/
|
||||
public interface PullingListener {
|
||||
|
||||
void startAnimation();
|
||||
|
||||
void stopAnimation();
|
||||
|
||||
/**
|
||||
* Set the amount of rotation to apply to the progress spinner.
|
||||
*
|
||||
* @param rotation Rotation is from [0..2]
|
||||
*/
|
||||
void setProgressRotation(float rotation);
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
package pub.doric.refresh;
|
||||
|
||||
import com.github.pengfeizhou.jscore.JSObject;
|
||||
import com.github.pengfeizhou.jscore.JSValue;
|
||||
import com.github.pengfeizhou.jscore.JavaValue;
|
||||
|
||||
import pub.doric.DoricContext;
|
||||
import pub.doric.extension.bridge.DoricMethod;
|
||||
import pub.doric.extension.bridge.DoricPlugin;
|
||||
import pub.doric.extension.bridge.DoricPromise;
|
||||
import pub.doric.shader.SuperNode;
|
||||
import pub.doric.shader.ViewNode;
|
||||
|
||||
/**
|
||||
* @Description: pub.doric.pullable
|
||||
* @Author: pengfei.zhou
|
||||
* @CreateDate: 2019-11-26
|
||||
*/
|
||||
@DoricPlugin(name = "Refreshable")
|
||||
public class RefreshableNode extends SuperNode<DoricSwipeLayout> implements PullingListener {
|
||||
|
||||
private String mContentViewId;
|
||||
private ViewNode mContentNode;
|
||||
|
||||
private String mHeaderViewId;
|
||||
private ViewNode mHeaderNode;
|
||||
|
||||
public RefreshableNode(DoricContext doricContext) {
|
||||
super(doricContext);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected DoricSwipeLayout build() {
|
||||
DoricSwipeLayout doricSwipeLayout = new DoricSwipeLayout(getContext());
|
||||
doricSwipeLayout.getRefreshView().setPullingListenr(this);
|
||||
return doricSwipeLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blend(DoricSwipeLayout view, String name, JSValue prop) {
|
||||
if ("content".equals(name)) {
|
||||
mContentViewId = prop.asString().value();
|
||||
} else if ("header".equals(name)) {
|
||||
mHeaderViewId = prop.asString().value();
|
||||
} else if ("onRefresh".equals(name)) {
|
||||
final String funcId = prop.asString().value();
|
||||
mView.setOnRefreshListener(new DoricSwipeLayout.OnRefreshListener() {
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
callJSResponse(funcId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
super.blend(view, name, prop);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blend(JSObject jsObject) {
|
||||
super.blend(jsObject);
|
||||
blendContentNode();
|
||||
blendHeadNode();
|
||||
}
|
||||
|
||||
|
||||
private void blendContentNode() {
|
||||
JSObject contentModel = getSubModel(mContentViewId);
|
||||
if (contentModel == null) {
|
||||
return;
|
||||
}
|
||||
String viewId = contentModel.getProperty("id").asString().value();
|
||||
String type = contentModel.getProperty("type").asString().value();
|
||||
JSObject props = contentModel.getProperty("props").asObject();
|
||||
if (mContentNode != null) {
|
||||
if (mContentNode.getId().equals(viewId)) {
|
||||
//skip
|
||||
} else {
|
||||
if (mReusable && type.equals(mContentNode.getType())) {
|
||||
mContentNode.setId(viewId);
|
||||
mContentNode.blend(props);
|
||||
} else {
|
||||
mView.removeAllViews();
|
||||
mContentNode = ViewNode.create(getDoricContext(), type);
|
||||
mContentNode.setId(viewId);
|
||||
mContentNode.init(this);
|
||||
mContentNode.blend(props);
|
||||
mView.addView(mContentNode.getDoricLayer());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mContentNode = ViewNode.create(getDoricContext(), type);
|
||||
mContentNode.setId(viewId);
|
||||
mContentNode.init(this);
|
||||
mContentNode.blend(props);
|
||||
mView.addView(mContentNode.getDoricLayer());
|
||||
}
|
||||
}
|
||||
|
||||
private void blendHeadNode() {
|
||||
JSObject headerModel = getSubModel(mHeaderViewId);
|
||||
if (headerModel == null) {
|
||||
return;
|
||||
}
|
||||
String viewId = headerModel.getProperty("id").asString().value();
|
||||
String type = headerModel.getProperty("type").asString().value();
|
||||
JSObject props = headerModel.getProperty("props").asObject();
|
||||
if (mHeaderNode != null) {
|
||||
if (mHeaderNode.getId().equals(viewId)) {
|
||||
//skip
|
||||
} else {
|
||||
if (mReusable && type.equals(mHeaderNode.getType())) {
|
||||
mHeaderNode.setId(viewId);
|
||||
mHeaderNode.blend(props);
|
||||
} else {
|
||||
mHeaderNode = ViewNode.create(getDoricContext(), type);
|
||||
mHeaderNode.setId(viewId);
|
||||
mHeaderNode.init(this);
|
||||
mHeaderNode.blend(props);
|
||||
mView.getRefreshView().setContent(mHeaderNode.getDoricLayer());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mHeaderNode = ViewNode.create(getDoricContext(), type);
|
||||
mHeaderNode.setId(viewId);
|
||||
mHeaderNode.init(this);
|
||||
mHeaderNode.blend(props);
|
||||
mView.getRefreshView().setContent(mHeaderNode.getDoricLayer());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewNode getSubNodeById(String id) {
|
||||
if (id.equals(mContentViewId)) {
|
||||
return mContentNode;
|
||||
}
|
||||
if (id.equals(mHeaderViewId)) {
|
||||
return mHeaderNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blendSubNode(JSObject subProperties) {
|
||||
String viewId = subProperties.getProperty("id").asString().value();
|
||||
ViewNode node = getSubNodeById(viewId);
|
||||
if (node != null) {
|
||||
node.blend(subProperties.getProperty("props").asObject());
|
||||
}
|
||||
}
|
||||
|
||||
@DoricMethod
|
||||
public void setRefreshable(JSValue jsValue, DoricPromise doricPromise) {
|
||||
boolean refreshable = jsValue.asBoolean().value();
|
||||
this.mView.setEnabled(refreshable);
|
||||
doricPromise.resolve();
|
||||
}
|
||||
|
||||
@DoricMethod
|
||||
public void setRefreshing(JSValue jsValue, DoricPromise doricPromise) {
|
||||
boolean refreshing = jsValue.asBoolean().value();
|
||||
this.mView.setRefreshing(refreshing);
|
||||
doricPromise.resolve();
|
||||
}
|
||||
|
||||
@DoricMethod
|
||||
public void isRefreshable(DoricPromise doricPromise) {
|
||||
doricPromise.resolve(new JavaValue(this.mView.isEnabled()));
|
||||
}
|
||||
|
||||
@DoricMethod
|
||||
public void isRefreshing(DoricPromise doricPromise) {
|
||||
doricPromise.resolve(new JavaValue(this.mView.isRefreshing()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startAnimation() {
|
||||
if (mHeaderNode != null) {
|
||||
mHeaderNode.callJSResponse("startAnimation");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopAnimation() {
|
||||
if (mHeaderNode != null) {
|
||||
mHeaderNode.callJSResponse("stopAnimation");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgressRotation(float rotation) {
|
||||
if (mHeaderNode != null) {
|
||||
mHeaderNode.callJSResponse("setProgressRotation", rotation);
|
||||
}
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ import com.bumptech.glide.request.target.Target;
|
||||
|
||||
import pub.doric.DoricContext;
|
||||
import pub.doric.extension.bridge.DoricPlugin;
|
||||
import pub.doric.utils.DoricUtils;
|
||||
|
||||
import com.github.pengfeizhou.jscore.JSONBuilder;
|
||||
import com.github.pengfeizhou.jscore.JSValue;
|
||||
@ -77,8 +78,8 @@ public class ImageNode extends ViewNode<ImageView> {
|
||||
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
|
||||
if (!TextUtils.isEmpty(loadCallbackId)) {
|
||||
callJSResponse(loadCallbackId, new JSONBuilder()
|
||||
.put("width", resource.getIntrinsicWidth())
|
||||
.put("height", resource.getIntrinsicHeight())
|
||||
.put("width", DoricUtils.px2dp(resource.getIntrinsicWidth()))
|
||||
.put("height", DoricUtils.px2dp(resource.getIntrinsicHeight()))
|
||||
.toJSONObject());
|
||||
}
|
||||
return false;
|
||||
|
@ -257,4 +257,21 @@ public abstract class ViewNode<T extends View> extends DoricContextHolder {
|
||||
public int getHeight() {
|
||||
return mView.getHeight();
|
||||
}
|
||||
|
||||
@DoricMethod
|
||||
public void setRotation(JSValue jsValue) {
|
||||
float rotation = jsValue.asNumber().toFloat();
|
||||
while (rotation > 1) {
|
||||
rotation = rotation - 1;
|
||||
}
|
||||
while (rotation < -1) {
|
||||
rotation = rotation + 1;
|
||||
}
|
||||
doricLayer.setRotation(rotation * 360);
|
||||
}
|
||||
|
||||
@DoricMethod
|
||||
public float getRotation() {
|
||||
return doricLayer.getRotation() / 360;
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ public class DoricConstant {
|
||||
public static final String INJECT_TIMER_SET = "nativeSetTimer";
|
||||
public static final String INJECT_TIMER_CLEAR = "nativeClearTimer";
|
||||
public static final String INJECT_BRIDGE = "nativeBridge";
|
||||
public static final String INJECT_EMPTY = "nativeEmpty";
|
||||
|
||||
public static final String TEMPLATE_CONTEXT_CREATE = "Reflect.apply(" +
|
||||
"function(doric,context,Entry,require,exports){" + "\n" +
|
||||
|
@ -81,6 +81,8 @@ public class DoricUtils {
|
||||
return new JavaValue((Integer) arg);
|
||||
} else if (arg instanceof Long) {
|
||||
return new JavaValue((Long) arg);
|
||||
} else if (arg instanceof Float) {
|
||||
return new JavaValue((Float) arg);
|
||||
} else if (arg instanceof Double) {
|
||||
return new JavaValue((Double) arg);
|
||||
} else if (arg instanceof Boolean) {
|
||||
|
@ -12,4 +12,5 @@ export default [
|
||||
'src/StorageDemo',
|
||||
'src/NavigatorDemo',
|
||||
'src/NavbarDemo',
|
||||
'src/RefreshableDemo',
|
||||
]
|
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
|
||||
import { Group, Panel, Text, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout, Gravity, hlayout, slider, slideItem, scroller, IVLayout, IHLayout } from "doric";
|
||||
import { Group, Panel, Text, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout, Gravity, hlayout, slider, slideItem, scroller, IVLayout, IHLayout, layoutConfig } from "doric";
|
||||
import { O_TRUNC } from "constants";
|
||||
|
||||
const colors = [
|
||||
@ -453,11 +453,7 @@ class LayoutDemo extends Panel {
|
||||
it.space = 20
|
||||
}),
|
||||
).also(it => {
|
||||
it.layoutConfig = {
|
||||
widthSpec: LayoutSpec.WRAP_CONTENT,
|
||||
heightSpec: LayoutSpec.WRAP_CONTENT,
|
||||
alignment: gravity().centerX(),
|
||||
}
|
||||
it.layoutConfig = layoutConfig().atmost()
|
||||
})
|
||||
.in(rootView)
|
||||
}
|
||||
|
@ -1,15 +1,10 @@
|
||||
import { Group, Panel, List, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout, Gravity, hlayout, Text } from "doric";
|
||||
const colors = [
|
||||
"#f0932b",
|
||||
"#eb4d4b",
|
||||
"#6ab04c",
|
||||
"#e056fd",
|
||||
"#686de0",
|
||||
"#30336b",
|
||||
]
|
||||
import { Group, Panel, List, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout, Gravity, hlayout, Text, refreshable, Refreshable, ListItem } from "doric";
|
||||
import { rotatedArrow, colors } from "./utils";
|
||||
@Entry
|
||||
class ListPanel extends Panel {
|
||||
build(rootView: Group): void {
|
||||
let refreshView: Refreshable
|
||||
let offset = Math.ceil(Math.random() * colors.length)
|
||||
vlayout([
|
||||
text({
|
||||
text: "ListDemo",
|
||||
@ -23,9 +18,14 @@ class ListPanel extends Panel {
|
||||
textAlignment: gravity().center(),
|
||||
height: 50,
|
||||
}),
|
||||
list({
|
||||
itemCount: 1000,
|
||||
renderItem: (idx: number) => {
|
||||
refreshView = refreshable({
|
||||
onRefresh: () => {
|
||||
refreshView.setRefreshing(context, false).then(() => {
|
||||
(refreshView.content as List).also(it => {
|
||||
it.reset()
|
||||
offset = Math.ceil(Math.random() * colors.length)
|
||||
it.itemCount = 40
|
||||
it.renderItem = (idx: number) => {
|
||||
let counter!: Text
|
||||
return listItem(
|
||||
hlayout([
|
||||
@ -64,7 +64,7 @@ class ListPanel extends Panel {
|
||||
}
|
||||
}
|
||||
it.gravity = gravity().center()
|
||||
it.bgColor = Color.parse(colors[idx % colors.length])
|
||||
it.bgColor = colors[(idx + offset) % colors.length]
|
||||
let clicked = 0
|
||||
it.onClick = () => {
|
||||
counter.text = `Item Clicked ${++clicked}`
|
||||
@ -87,12 +87,21 @@ class ListPanel extends Panel {
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
header: rotatedArrow(context),
|
||||
content: list({
|
||||
itemCount: 0,
|
||||
renderItem: () => new ListItem,
|
||||
layoutConfig: {
|
||||
widthSpec: LayoutSpec.AT_MOST,
|
||||
heightSpec: LayoutSpec.AT_MOST,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
|
||||
]).also(it => {
|
||||
it.layoutConfig = {
|
||||
widthSpec: LayoutSpec.AT_MOST,
|
||||
@ -100,5 +109,6 @@ class ListPanel extends Panel {
|
||||
}
|
||||
it.bgColor = Color.WHITE
|
||||
}).in(rootView)
|
||||
refreshView.bgColor = Color.YELLOW
|
||||
}
|
||||
}
|
109
demo/src/RefreshableDemo.ts
Normal file
109
demo/src/RefreshableDemo.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { refreshable, Group, Panel, pullable, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout, Gravity, hlayout, Text, scroller, layoutConfig, image, IView, IVLayout, ScaleType, modal, IText, network, navigator, stack, Image } from "doric";
|
||||
import { title, label, colors, icon_refresh } from "./utils";
|
||||
|
||||
@Entry
|
||||
class RefreshableDemo extends Panel {
|
||||
build(rootView: Group): void {
|
||||
let refreshImage: Image
|
||||
let refreshView = refreshable({
|
||||
layoutConfig: layoutConfig().atmost(),
|
||||
onRefresh: () => {
|
||||
log('onRefresh')
|
||||
setTimeout(() => {
|
||||
refreshView.setRefreshing(context, false)
|
||||
}, 5000)
|
||||
},
|
||||
header: pullable(context,
|
||||
stack([
|
||||
image({
|
||||
layoutConfig: layoutConfig().exactly().m({ top: 50, bottom: 10, }),
|
||||
width: 30,
|
||||
height: 30,
|
||||
imageBase64: icon_refresh,
|
||||
}).also(v => {
|
||||
refreshImage = v
|
||||
}),
|
||||
]),
|
||||
{
|
||||
startAnimation: () => {
|
||||
log('startAnimation')
|
||||
},
|
||||
stopAnimation: () => {
|
||||
log('stopAnimation')
|
||||
},
|
||||
setProgressRotation: (rotation: number) => {
|
||||
refreshImage.setRotation(context, rotation)
|
||||
},
|
||||
}),
|
||||
content: scroller(vlayout([
|
||||
title("Refreshable Demo"),
|
||||
label('start Refresh').apply({
|
||||
width: 300,
|
||||
height: 50,
|
||||
bgColor: colors[0],
|
||||
textSize: 30,
|
||||
textColor: Color.WHITE,
|
||||
layoutConfig: layoutConfig().exactly(),
|
||||
onClick: () => {
|
||||
refreshView.setRefreshing(context, true)
|
||||
}
|
||||
} as IText),
|
||||
label('stop Refresh').apply({
|
||||
width: 300,
|
||||
height: 50,
|
||||
bgColor: colors[0],
|
||||
textSize: 30,
|
||||
textColor: Color.WHITE,
|
||||
layoutConfig: layoutConfig().exactly(),
|
||||
onClick: () => {
|
||||
refreshView.setRefreshing(context, false)
|
||||
}
|
||||
} as IText),
|
||||
|
||||
label('Enable Refresh').apply({
|
||||
width: 300,
|
||||
height: 50,
|
||||
bgColor: colors[0],
|
||||
textSize: 30,
|
||||
textColor: Color.WHITE,
|
||||
layoutConfig: layoutConfig().exactly(),
|
||||
onClick: () => {
|
||||
refreshView.setRefreshable(context, true)
|
||||
}
|
||||
} as IText),
|
||||
|
||||
label('Disable Refresh').apply({
|
||||
width: 300,
|
||||
height: 50,
|
||||
bgColor: colors[0],
|
||||
textSize: 30,
|
||||
textColor: Color.WHITE,
|
||||
layoutConfig: layoutConfig().exactly(),
|
||||
onClick: () => {
|
||||
refreshView.setRefreshable(context, false)
|
||||
}
|
||||
} as IText),
|
||||
label('Rotate self').apply({
|
||||
width: 300,
|
||||
height: 50,
|
||||
bgColor: colors[0],
|
||||
textSize: 30,
|
||||
textColor: Color.WHITE,
|
||||
layoutConfig: layoutConfig().exactly(),
|
||||
} as IText).also(v => {
|
||||
v.onClick = () => {
|
||||
v.nativeChannel(context, "setRotation")(0.25)
|
||||
}
|
||||
}),
|
||||
]).apply({
|
||||
layoutConfig: layoutConfig().atmost().h(LayoutSpec.WRAP_CONTENT),
|
||||
gravity: gravity().center(),
|
||||
space: 10,
|
||||
} as IVLayout)).apply({
|
||||
layoutConfig: layoutConfig().atmost(),
|
||||
})
|
||||
}).apply({
|
||||
bgColor: Color.YELLOW
|
||||
}).in(rootView)
|
||||
}
|
||||
}
|
@ -1,59 +1,34 @@
|
||||
import { Group, Panel, List, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout, Gravity, hlayout, scroller } from "doric";
|
||||
const colors = [
|
||||
"#f0932b",
|
||||
"#eb4d4b",
|
||||
"#6ab04c",
|
||||
"#e056fd",
|
||||
"#686de0",
|
||||
"#30336b",
|
||||
]
|
||||
import { Group, Panel, List, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout, Gravity, hlayout, scroller, layoutConfig } from "doric";
|
||||
import { label } from "./utils";
|
||||
|
||||
@Entry
|
||||
class ScrollerPanel extends Panel {
|
||||
build(rootView: Group): void {
|
||||
rootView.addChild(scroller(vlayout(
|
||||
[
|
||||
// ...[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5].map(e => text({
|
||||
// text: colors[e % colors.length],
|
||||
// textColor: Color.parse('#ffffff'),
|
||||
// textSize: 20,
|
||||
// bgColor: Color.parse(colors[e % colors.length]),
|
||||
// layoutConfig: {
|
||||
// widthSpec: LayoutSpec.EXACTLY,
|
||||
// heightSpec: LayoutSpec.EXACTLY,
|
||||
// },
|
||||
// width: 200,
|
||||
// height: 50,
|
||||
// })),
|
||||
...[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5].map(i => hlayout([
|
||||
...[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5].map(j => text({
|
||||
text: colors[(i + j) % colors.length],
|
||||
textColor: Color.parse('#ffffff'),
|
||||
textSize: 20,
|
||||
bgColor: Color.parse(colors[(i + j) % colors.length]),
|
||||
layoutConfig: {
|
||||
widthSpec: LayoutSpec.EXACTLY,
|
||||
heightSpec: LayoutSpec.EXACTLY,
|
||||
},
|
||||
width: 80,
|
||||
height: 50,
|
||||
})),
|
||||
]).also(it => it.space = 20)),
|
||||
hlayout([
|
||||
...[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5].map(e => text({
|
||||
text: colors[e % colors.length],
|
||||
textColor: Color.parse('#ffffff'),
|
||||
textSize: 20,
|
||||
bgColor: Color.parse(colors[e % colors.length]),
|
||||
layoutConfig: {
|
||||
widthSpec: LayoutSpec.EXACTLY,
|
||||
heightSpec: LayoutSpec.EXACTLY,
|
||||
},
|
||||
width: 200,
|
||||
height: 50,
|
||||
})),
|
||||
]
|
||||
),
|
||||
]
|
||||
).also(it => it.space = 20)))
|
||||
scroller(
|
||||
vlayout([
|
||||
scroller(
|
||||
vlayout(new Array(100).fill(1).map(e => label('Scroll Content')))
|
||||
).apply({
|
||||
layoutConfig: layoutConfig().exactly(),
|
||||
width: 300,
|
||||
height: 500,
|
||||
bgColor: Color.RED,
|
||||
}),
|
||||
scroller(
|
||||
vlayout(new Array(100).fill(1).map(e => label('Scroll Content')))
|
||||
).apply({
|
||||
layoutConfig: layoutConfig().exactly(),
|
||||
width: 300,
|
||||
height: 500,
|
||||
bgColor: Color.BLUE,
|
||||
})
|
||||
])
|
||||
)
|
||||
.apply({
|
||||
layoutConfig: layoutConfig().atmost().h(LayoutSpec.EXACTLY),
|
||||
height: 500,
|
||||
bgColor: Color.YELLOW,
|
||||
})
|
||||
.in(rootView)
|
||||
}
|
||||
}
|
2
demo/src/image_base64.ts
Normal file
2
demo/src/image_base64.ts
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -4,7 +4,7 @@
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
"lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
|
@ -39,8 +39,9 @@ TODO: Add long description of the pod here.
|
||||
s.public_header_files = 'Pod/Classes/**/*.h'
|
||||
# s.frameworks = 'UIKit', 'MapKit'
|
||||
# s.dependency 'AFNetworking', '~> 2.3'
|
||||
s.dependency 'SDWebImage', '~> 4.4.7'
|
||||
s.dependency 'SDWebImage/WebP'
|
||||
# s.dependency 'SDWebImage', '~> 5.0'
|
||||
s.dependency 'YYWebImage', '~>1.0.5'
|
||||
s.dependency 'YYImage/WebP'
|
||||
s.dependency 'SocketRocket', '~> 0.5.1'
|
||||
s.dependency 'GCDWebServer', '~> 3.0'
|
||||
s.dependency 'YYCache', '~> 1.0.4'
|
||||
|
@ -1,30 +1,23 @@
|
||||
PODS:
|
||||
- Doric (0.1.0):
|
||||
- GCDWebServer (~> 3.0)
|
||||
- SDWebImage (~> 4.4.7)
|
||||
- SDWebImage/WebP
|
||||
- SocketRocket (~> 0.5.1)
|
||||
- YYCache (~> 1.0.4)
|
||||
- YYImage/WebP
|
||||
- YYWebImage
|
||||
- GCDWebServer (3.5.3):
|
||||
- GCDWebServer/Core (= 3.5.3)
|
||||
- GCDWebServer/Core (3.5.3)
|
||||
- libwebp (1.0.3):
|
||||
- libwebp/demux (= 1.0.3)
|
||||
- libwebp/mux (= 1.0.3)
|
||||
- libwebp/webp (= 1.0.3)
|
||||
- libwebp/demux (1.0.3):
|
||||
- libwebp/webp
|
||||
- libwebp/mux (1.0.3):
|
||||
- libwebp/demux
|
||||
- libwebp/webp (1.0.3)
|
||||
- SDWebImage (4.4.7):
|
||||
- SDWebImage/Core (= 4.4.7)
|
||||
- SDWebImage/Core (4.4.7)
|
||||
- SDWebImage/WebP (4.4.7):
|
||||
- libwebp (< 2.0, >= 0.5)
|
||||
- SDWebImage/Core
|
||||
- SocketRocket (0.5.1)
|
||||
- YYCache (1.0.4)
|
||||
- YYImage (1.0.4):
|
||||
- YYImage/Core (= 1.0.4)
|
||||
- YYImage/Core (1.0.4)
|
||||
- YYImage/WebP (1.0.4):
|
||||
- YYImage/Core
|
||||
- YYWebImage (1.0.5):
|
||||
- YYCache
|
||||
- YYImage
|
||||
|
||||
DEPENDENCIES:
|
||||
- Doric (from `../`)
|
||||
@ -32,22 +25,22 @@ DEPENDENCIES:
|
||||
SPEC REPOS:
|
||||
https://github.com/cocoapods/specs.git:
|
||||
- GCDWebServer
|
||||
- libwebp
|
||||
- SDWebImage
|
||||
- SocketRocket
|
||||
- YYCache
|
||||
- YYImage
|
||||
- YYWebImage
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Doric:
|
||||
:path: "../"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Doric: c71287d68afeeb79bfd3c680ed2dd3b90d515c12
|
||||
Doric: e73b17b0e46198994f5c3d8af49f26fd9f49df09
|
||||
GCDWebServer: c0ab22c73e1b84f358d1e2f74bf6afd1c60829f2
|
||||
libwebp: 057912d6d0abfb6357d8bb05c0ea470301f5d61e
|
||||
SDWebImage: c10d14a8883ebd89664f02a422006f66a85c0c5d
|
||||
SocketRocket: d57c7159b83c3c6655745cd15302aa24b6bae531
|
||||
YYCache: 8105b6638f5e849296c71f331ff83891a4942952
|
||||
YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54
|
||||
YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928
|
||||
|
||||
PODFILE CHECKSUM: 012563d71439e7e33e976dca3b59664ed56cee39
|
||||
|
||||
|
@ -25,3 +25,4 @@
|
||||
#import "DoricNavigatorDelegate.h"
|
||||
#import "DoricNavBarDelegate.h"
|
||||
#import "DoricViewController.h"
|
||||
#import "DoricPromise.h"
|
@ -37,6 +37,7 @@
|
||||
#import "DoricStoragePlugin.h"
|
||||
#import "DoricNavigatorPlugin.h"
|
||||
#import "DoricNavBarPlugin.h"
|
||||
#import "DoricRefreshableNode.h"
|
||||
|
||||
@interface DoricRegistry ()
|
||||
|
||||
@ -76,6 +77,7 @@ - (void)innerRegister {
|
||||
[self registerViewNode:DoricScrollerNode.class withName:@"Scroller"];
|
||||
[self registerViewNode:DoricSliderNode.class withName:@"Slider"];
|
||||
[self registerViewNode:DoricSlideItemNode.class withName:@"SlideItem"];
|
||||
[self registerViewNode:DoricRefreshableNode.class withName:@"Refreshable"];
|
||||
}
|
||||
|
||||
- (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name {
|
||||
|
@ -59,7 +59,9 @@ - (void)initJSExecutor {
|
||||
[self.jsExecutor injectGlobalJSObject:INJECT_LOG obj:^(NSString *type, NSString *message) {
|
||||
DoricLog(@"JS:%@", message);
|
||||
}];
|
||||
[self.jsExecutor injectGlobalJSObject:INJECT_EMPTY obj:^() {
|
||||
|
||||
}];
|
||||
[self.jsExecutor injectGlobalJSObject:INJECT_REQUIRE obj:^(NSString *name) {
|
||||
__strong typeof(_self) self = _self;
|
||||
if (!self) return NO;
|
||||
|
@ -25,8 +25,6 @@
|
||||
#import "DoricUtil.h"
|
||||
#import "Doric.h"
|
||||
|
||||
#import <JavaScriptCore/JavaScriptCore.h>
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation DoricShaderPlugin
|
||||
@ -110,9 +108,13 @@ - (id)findClass:(Class)clz target:(id)target method:(NSString *)name promise:(Do
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
void *retValue;
|
||||
block();
|
||||
const char *retType = methodSignature.methodReturnType;
|
||||
if (!strcmp(retType, @encode(void))) {
|
||||
} else {
|
||||
[invocation getReturnValue:&retValue];
|
||||
id returnValue = (__bridge id) retValue;
|
||||
[promise resolve:returnValue];
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
10
iOS/Pod/Classes/Refresh/DoricRefreshableNode.h
Normal file
10
iOS/Pod/Classes/Refresh/DoricRefreshableNode.h
Normal file
@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by pengfei.zhou on 2019/11/26.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "DoricSuperNode.h"
|
||||
#import "DoricSwipeRefreshLayout.h"
|
||||
|
||||
@interface DoricRefreshableNode : DoricSuperNode<DoricSwipeRefreshLayout *>
|
||||
@end
|
161
iOS/Pod/Classes/Refresh/DoricRefreshableNode.m
Normal file
161
iOS/Pod/Classes/Refresh/DoricRefreshableNode.m
Normal file
@ -0,0 +1,161 @@
|
||||
//
|
||||
// Created by pengfei.zhou on 2019/11/26.
|
||||
//
|
||||
|
||||
#import "DoricRefreshableNode.h"
|
||||
#import "Doric.h"
|
||||
|
||||
@interface DoricRefreshableNode () <DoricSwipePullingDelegate>
|
||||
@property(nonatomic, strong) DoricViewNode *contentNode;
|
||||
@property(nonatomic, copy) NSString *contentViewId;
|
||||
@property(nonatomic, strong) DoricViewNode *headerNode;
|
||||
@property(nonatomic, copy) NSString *headerViewId;
|
||||
@end
|
||||
|
||||
@implementation DoricRefreshableNode
|
||||
- (DoricSwipeRefreshLayout *)build {
|
||||
return [[DoricSwipeRefreshLayout new] also:^(DoricSwipeRefreshLayout *it) {
|
||||
it.swipePullingDelegate = self;
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)blendView:(DoricSwipeRefreshLayout *)view forPropName:(NSString *)name propValue:(id)prop {
|
||||
if ([@"content" isEqualToString:name]) {
|
||||
self.contentViewId = prop;
|
||||
} else if ([@"header" isEqualToString:name]) {
|
||||
self.headerViewId = prop;
|
||||
} else if ([@"onRefresh" isEqualToString:name]) {
|
||||
__weak typeof(self) _self = self;
|
||||
NSString *funcId = prop;
|
||||
self.view.onRefreshBlock = ^{
|
||||
__strong typeof(_self) self = _self;
|
||||
[self callJSResponse:funcId, nil];
|
||||
};
|
||||
} else {
|
||||
[super blendView:view forPropName:name propValue:prop];
|
||||
}
|
||||
}
|
||||
|
||||
- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId {
|
||||
if ([viewId isEqualToString:self.contentViewId]) {
|
||||
return self.contentNode;
|
||||
} else if ([viewId isEqualToString:self.headerViewId]) {
|
||||
return self.headerNode;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)blend:(NSDictionary *)props {
|
||||
[super blend:props];
|
||||
[self blendHeader];
|
||||
[self blendContent];
|
||||
}
|
||||
|
||||
- (void)blendContent {
|
||||
NSDictionary *contentModel = [self subModelOf:self.contentViewId];
|
||||
if (!contentModel) {
|
||||
return;
|
||||
}
|
||||
NSString *viewId = contentModel[@"id"];
|
||||
NSString *type = contentModel[@"type"];
|
||||
NSDictionary *childProps = contentModel[@"props"];
|
||||
if (self.contentNode) {
|
||||
if ([self.contentNode.viewId isEqualToString:viewId]) {
|
||||
//skip
|
||||
} else {
|
||||
if (self.reusable && [type isEqualToString:self.contentNode.type]) {
|
||||
[self.contentNode also:^(DoricViewNode *it) {
|
||||
it.viewId = viewId;
|
||||
[it blend:childProps];
|
||||
}];
|
||||
} else {
|
||||
self.contentNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
|
||||
it.viewId = viewId;
|
||||
[it initWithSuperNode:self];
|
||||
[it blend:childProps];
|
||||
self.view.contentView = it.view;
|
||||
}];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.contentNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
|
||||
it.viewId = viewId;
|
||||
[it initWithSuperNode:self];
|
||||
[it blend:childProps];
|
||||
self.view.contentView = it.view;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)blendHeader {
|
||||
NSDictionary *headerModel = [self subModelOf:self.headerViewId];
|
||||
if (!headerModel) {
|
||||
return;
|
||||
}
|
||||
NSString *viewId = headerModel[@"id"];
|
||||
NSString *type = headerModel[@"type"];
|
||||
NSDictionary *childProps = headerModel[@"props"];
|
||||
if (self.headerNode) {
|
||||
if ([self.headerNode.viewId isEqualToString:viewId]) {
|
||||
//skip
|
||||
} else {
|
||||
if (self.reusable && [type isEqualToString:self.headerNode.type]) {
|
||||
[self.headerNode also:^(DoricViewNode *it) {
|
||||
it.viewId = viewId;
|
||||
[it blend:childProps];
|
||||
}];
|
||||
} else {
|
||||
self.headerNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
|
||||
it.viewId = viewId;
|
||||
[it initWithSuperNode:self];
|
||||
[it blend:childProps];
|
||||
self.view.headerView = it.view;
|
||||
}];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.headerNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
|
||||
it.viewId = viewId;
|
||||
[it initWithSuperNode:self];
|
||||
[it blend:childProps];
|
||||
self.view.headerView = it.view;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)blendSubNode:(NSDictionary *)subModel {
|
||||
[[self subNodeWithViewId:subModel[@"id"]] blend:subModel[@"props"]];
|
||||
}
|
||||
|
||||
- (void)startAnimation {
|
||||
[self.headerNode callJSResponse:@"startAnimation", nil];
|
||||
}
|
||||
|
||||
- (void)stopAnimation {
|
||||
[self.headerNode callJSResponse:@"stopAnimation", nil];
|
||||
}
|
||||
|
||||
- (void)setProgressRotation:(CGFloat)rotation {
|
||||
[self.headerNode callJSResponse:@"setProgressRotation", @(rotation), nil];
|
||||
}
|
||||
|
||||
- (void)setRefreshing:(NSNumber *)refreshable withPromise:(DoricPromise *)promise {
|
||||
self.view.refreshing = [refreshable boolValue];
|
||||
[promise resolve:nil];
|
||||
}
|
||||
|
||||
- (void)setRefreshable:(NSNumber *)refreshing withPromise:(DoricPromise *)promise {
|
||||
self.view.refreshable = [refreshing boolValue];
|
||||
[promise resolve:nil];
|
||||
}
|
||||
|
||||
- (NSNumber *)isRefreshing {
|
||||
return @(self.view.refreshing);
|
||||
}
|
||||
|
||||
- (NSNumber *)isRefreshable {
|
||||
return @(self.view.refreshable);
|
||||
}
|
||||
@end
|
22
iOS/Pod/Classes/Refresh/DoricSwipeRefreshLayout.h
Normal file
22
iOS/Pod/Classes/Refresh/DoricSwipeRefreshLayout.h
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// Created by pengfei.zhou on 2019/11/26.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol DoricSwipePullingDelegate <NSObject>
|
||||
- (void)startAnimation;
|
||||
|
||||
- (void)stopAnimation;
|
||||
|
||||
- (void)setProgressRotation:(CGFloat)rotation;
|
||||
@end
|
||||
|
||||
@interface DoricSwipeRefreshLayout : UIScrollView
|
||||
@property(nonatomic, strong) UIView *contentView;
|
||||
@property(nonatomic, strong) UIView *headerView;
|
||||
@property(nonatomic, assign) BOOL refreshable;
|
||||
@property(nonatomic, assign) BOOL refreshing;
|
||||
@property(nonatomic, strong) void (^onRefreshBlock)(void);
|
||||
@property(nonatomic, weak) id <DoricSwipePullingDelegate> swipePullingDelegate;
|
||||
@end
|
146
iOS/Pod/Classes/Refresh/DoricSwipeRefreshLayout.m
Normal file
146
iOS/Pod/Classes/Refresh/DoricSwipeRefreshLayout.m
Normal file
@ -0,0 +1,146 @@
|
||||
//
|
||||
// Created by pengfei.zhou on 2019/11/26.
|
||||
//
|
||||
|
||||
#import "DoricSwipeRefreshLayout.h"
|
||||
#import "UIView+Doric.h"
|
||||
#import "DoricLayouts.h"
|
||||
|
||||
@interface DoricSwipeRefreshLayout () <UIScrollViewDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation DoricSwipeRefreshLayout
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.showsHorizontalScrollIndicator = NO;
|
||||
self.showsVerticalScrollIndicator = NO;
|
||||
self.alwaysBounceVertical = YES;
|
||||
self.delegate = self;
|
||||
if (@available(iOS 11, *)) {
|
||||
self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
self.showsHorizontalScrollIndicator = NO;
|
||||
self.showsVerticalScrollIndicator = NO;
|
||||
self.alwaysBounceVertical = YES;
|
||||
self.delegate = self;
|
||||
if (@available(iOS 11, *)) {
|
||||
self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
if (self.contentView) {
|
||||
return [self.contentView measureSize:size];
|
||||
}
|
||||
return CGSizeZero;
|
||||
}
|
||||
|
||||
- (BOOL)requestFromSubview:(UIView *)subview {
|
||||
if (subview == self.headerView) {
|
||||
return NO;
|
||||
}
|
||||
return [super requestFromSubview:subview];
|
||||
}
|
||||
|
||||
- (void)layoutSelf:(CGSize)targetSize {
|
||||
[super layoutSelf:targetSize];
|
||||
self.headerView.bottom = 0;
|
||||
self.headerView.centerX = self.centerX;
|
||||
self.contentSize = self.frame.size;
|
||||
}
|
||||
|
||||
- (void)setContentView:(UIView *)contentView {
|
||||
if (_contentView) {
|
||||
[_contentView removeFromSuperview];
|
||||
}
|
||||
_contentView = contentView;
|
||||
[self addSubview:contentView];
|
||||
}
|
||||
|
||||
- (void)setHeaderView:(UIView *)headerView {
|
||||
if (_headerView) {
|
||||
[_headerView removeFromSuperview];
|
||||
}
|
||||
_headerView = headerView;
|
||||
[self addSubview:headerView];
|
||||
self.refreshable = YES;
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
|
||||
if (-scrollView.contentOffset.y >= self.headerView.height) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.refreshing = YES;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
if (scrollView.contentOffset.y <= 0) {
|
||||
[self.swipePullingDelegate setProgressRotation:-scrollView.contentOffset.y / self.headerView.height];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setContentOffset:(CGPoint)contentOffset {
|
||||
[super setContentOffset:contentOffset];
|
||||
}
|
||||
|
||||
- (void)setRefreshing:(BOOL)refreshing {
|
||||
if (_refreshing == refreshing) {
|
||||
return;
|
||||
}
|
||||
if (refreshing) {
|
||||
if (self.onRefreshBlock) {
|
||||
self.onRefreshBlock();
|
||||
}
|
||||
[UIView animateWithDuration:.3f
|
||||
animations:^{
|
||||
self.contentOffset = (CGPoint) {0, -self.headerView.height};
|
||||
self.contentInset = UIEdgeInsetsMake(self.headerView.height, 0, 0, 0);
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
[self.swipePullingDelegate startAnimation];
|
||||
self.scrollEnabled = NO;
|
||||
}
|
||||
];
|
||||
} else {
|
||||
self.bounces = YES;
|
||||
[UIView animateWithDuration:.3f
|
||||
animations:^{
|
||||
self.contentOffset = (CGPoint) {0, 0};
|
||||
self.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
[self.swipePullingDelegate stopAnimation];
|
||||
self.scrollEnabled = YES;
|
||||
}
|
||||
];
|
||||
}
|
||||
_refreshing = refreshing;
|
||||
}
|
||||
|
||||
- (void)setRefreshable:(BOOL)refreshable {
|
||||
self.scrollEnabled = refreshable;
|
||||
if (refreshable) {
|
||||
self.contentOffset = (CGPoint) {0, 0};
|
||||
self.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)refreshable {
|
||||
return self.scrollEnabled;
|
||||
}
|
||||
|
||||
- (void)setContentSize:(CGSize)contentSize {
|
||||
[super setContentSize:contentSize];
|
||||
}
|
||||
@end
|
@ -22,7 +22,7 @@
|
||||
|
||||
#import "DoricImageNode.h"
|
||||
#import "Doric.h"
|
||||
#import <SDWebImage/UIImageView+WebCache.h>
|
||||
#import "YYWebImage.h"
|
||||
|
||||
@interface DoricImageNode ()
|
||||
@property(nonatomic, copy) NSString *loadCallbackId;
|
||||
@ -31,7 +31,7 @@ @interface DoricImageNode ()
|
||||
@implementation DoricImageNode
|
||||
|
||||
- (UIImageView *)build {
|
||||
return [[UIImageView new] also:^(UIImageView *it) {
|
||||
return [[YYAnimatedImageView new] also:^(UIImageView *it) {
|
||||
it.clipsToBounds = YES;
|
||||
}];
|
||||
}
|
||||
@ -39,7 +39,7 @@ - (UIImageView *)build {
|
||||
- (void)blendView:(UIImageView *)view forPropName:(NSString *)name propValue:(id)prop {
|
||||
if ([@"imageUrl" isEqualToString:name]) {
|
||||
__weak typeof(self) _self = self;
|
||||
[view sd_setImageWithURL:[NSURL URLWithString:prop] completed:^(UIImage *_Nullable image, NSError *_Nullable error, SDImageCacheType cacheType, NSURL *_Nullable imageURL) {
|
||||
[view yy_setImageWithURL:[NSURL URLWithString:prop] placeholder:nil options:0 completion:^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
|
||||
__strong typeof(_self) self = _self;
|
||||
if (error) {
|
||||
if (self.loadCallbackId.length > 0) {
|
||||
@ -53,7 +53,6 @@ - (void)blendView:(UIImageView *)view forPropName:(NSString *)name propValue:(id
|
||||
}
|
||||
[self requestLayout];
|
||||
}
|
||||
|
||||
}];
|
||||
} else if ([@"scaleType" isEqualToString:name]) {
|
||||
switch ([prop integerValue]) {
|
||||
|
@ -19,14 +19,7 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
struct DoricMargin {
|
||||
CGFloat left;
|
||||
CGFloat right;
|
||||
CGFloat top;
|
||||
CGFloat bottom;
|
||||
};
|
||||
typedef struct DoricMargin DoricMargin;
|
||||
typedef UIEdgeInsets DoricMargin;
|
||||
|
||||
DoricMargin DoricMarginMake(CGFloat left, CGFloat top, CGFloat right, CGFloat bottom);
|
||||
|
||||
@ -68,17 +61,13 @@ typedef NS_ENUM(NSInteger, DoricGravity) {
|
||||
|
||||
|
||||
@interface DoricLayoutContainer : UIView
|
||||
@property(nonatomic, assign) DoricGravity gravity;
|
||||
|
||||
- (void)layout:(CGSize)targetSize;
|
||||
|
||||
- (CGSize)sizeContent:(CGSize)size;
|
||||
@end
|
||||
|
||||
@interface DoricStackView : DoricLayoutContainer
|
||||
@end
|
||||
|
||||
@interface DoricLinearView : DoricLayoutContainer
|
||||
@property(nonatomic, assign) DoricGravity gravity;
|
||||
@property(nonatomic, assign) CGFloat space;
|
||||
@end
|
||||
|
||||
@ -91,7 +80,20 @@ typedef NS_ENUM(NSInteger, DoricGravity) {
|
||||
|
||||
@interface UIView (DoricLayoutConfig)
|
||||
@property(nonatomic, strong) DoricLayoutConfig *layoutConfig;
|
||||
@end
|
||||
|
||||
@interface UIView (DoricTag)
|
||||
@property(nonatomic, copy) NSString *tagString;
|
||||
|
||||
- (UIView *)viewWithTagString:(NSString *)tagString;
|
||||
@end
|
||||
|
||||
@interface UIView (DoricLayouts)
|
||||
- (void)layoutSelf:(CGSize)targetSize;
|
||||
|
||||
- (CGSize)measureSize:(CGSize)targetSize;
|
||||
|
||||
- (void)doricLayoutSubviews;
|
||||
|
||||
- (BOOL)requestFromSubview:(UIView *)subview;
|
||||
@end
|
@ -19,8 +19,107 @@
|
||||
|
||||
#import "DoricLayouts.h"
|
||||
#import <objc/runtime.h>
|
||||
#import <Doric/DoricLayouts.h>
|
||||
#import "UIView+Doric.h"
|
||||
|
||||
static const void *kLayoutConfig = &kLayoutConfig;
|
||||
|
||||
@implementation UIView (DoricLayoutConfig)
|
||||
@dynamic layoutConfig;
|
||||
|
||||
- (void)setLayoutConfig:(DoricLayoutConfig *)layoutConfig {
|
||||
objc_setAssociatedObject(self, kLayoutConfig, layoutConfig, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
- (DoricLayoutConfig *)layoutConfig {
|
||||
return objc_getAssociatedObject(self, kLayoutConfig);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static const void *kTagString = &kTagString;
|
||||
|
||||
@implementation UIView (DoricTag)
|
||||
|
||||
- (void)setTagString:(NSString *)tagString {
|
||||
objc_setAssociatedObject(self, kTagString, tagString, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
self.tag = [tagString hash];
|
||||
}
|
||||
|
||||
- (NSString *)tagString {
|
||||
return objc_getAssociatedObject(self, kTagString);
|
||||
}
|
||||
|
||||
|
||||
- (UIView *)viewWithTagString:(NSString *)tagString {
|
||||
// notice the potential hash collision
|
||||
return [self viewWithTag:[tagString hash]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation UIView (DoricLayouts)
|
||||
/**
|
||||
* Measure self's size
|
||||
* */
|
||||
- (CGSize)measureSize:(CGSize)targetSize {
|
||||
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 = targetSize.width - config.margin.left - config.margin.right;
|
||||
}
|
||||
if (config.heightSpec == DoricLayoutAtMost
|
||||
|| config.heightSpec == DoricLayoutWrapContent) {
|
||||
height = targetSize.height - config.margin.top - config.margin.bottom;
|
||||
}
|
||||
|
||||
CGSize contentSize = [self sizeThatFits:CGSizeMake(width, height)];
|
||||
if (config.widthSpec == DoricLayoutWrapContent) {
|
||||
width = contentSize.width;
|
||||
}
|
||||
if (config.heightSpec == DoricLayoutWrapContent) {
|
||||
height = contentSize.height;
|
||||
}
|
||||
return CGSizeMake(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* layout self and subviews
|
||||
* */
|
||||
- (void)layoutSelf:(CGSize)targetSize {
|
||||
self.width = targetSize.width;
|
||||
self.height = targetSize.height;
|
||||
for (UIView *view in self.subviews) {
|
||||
[view layoutSelf:[view measureSize:targetSize]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)doricLayoutSubviews {
|
||||
if ([self.superview requestFromSubview:self]) {
|
||||
[self.superview doricLayoutSubviews];
|
||||
} else {
|
||||
[self layoutSelf:CGSizeMake(self.width, self.height)];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)requestFromSubview:(UIView *)subview {
|
||||
if (self.layoutConfig
|
||||
&& self.layoutConfig.widthSpec != DoricLayoutExact
|
||||
&& self.layoutConfig.heightSpec != DoricLayoutExact) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
@end
|
||||
|
||||
DoricMargin DoricMarginMake(CGFloat left, CGFloat top, CGFloat right, CGFloat bottom) {
|
||||
DoricMargin margin;
|
||||
margin.left = left;
|
||||
@ -65,63 +164,23 @@ @interface DoricLayoutContainer ()
|
||||
@end
|
||||
|
||||
@implementation DoricLayoutContainer
|
||||
- (void)setNeedsLayout {
|
||||
[super setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
if ([self.superview isKindOfClass:[DoricLayoutContainer class]]) {
|
||||
[self.superview layoutSubviews];
|
||||
} else {
|
||||
CGSize size = [self sizeThatFits:CGSizeMake(self.superview.width, self.superview.height)];
|
||||
[self layout:size];
|
||||
}
|
||||
}
|
||||
|
||||
- (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;
|
||||
[super layoutSubviews];
|
||||
[self doricLayoutSubviews];
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@interface DoricStackView ()
|
||||
|
||||
@property(nonatomic, assign) CGFloat contentWidth;
|
||||
@property(nonatomic, assign) CGFloat contentHeight;
|
||||
@end
|
||||
|
||||
@implementation DoricStackView
|
||||
|
||||
- (CGSize)sizeContent:(CGSize)size {
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
CGFloat contentWidth = 0;
|
||||
CGFloat contentHeight = 0;
|
||||
for (UIView *child in self.subviews) {
|
||||
@ -132,24 +191,11 @@ - (CGSize)sizeContent:(CGSize)size {
|
||||
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;
|
||||
CGSize childSize;
|
||||
if (CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
|
||||
childSize = [child measureSize:CGSizeMake(size.width, size.height)];
|
||||
} else {
|
||||
childSize = child.bounds.size;
|
||||
}
|
||||
contentWidth = MAX(contentWidth, childSize.width + childConfig.margin.left + childConfig.margin.right);
|
||||
contentHeight = MAX(contentHeight, childSize.height + childConfig.margin.top + childConfig.margin.bottom);
|
||||
@ -159,38 +205,23 @@ - (CGSize)sizeContent:(CGSize)size {
|
||||
return CGSizeMake(contentWidth, contentHeight);
|
||||
}
|
||||
|
||||
- (void)layout:(CGSize)targetSize {
|
||||
- (void)layoutSelf:(CGSize)targetSize {
|
||||
self.width = targetSize.width;
|
||||
self.height = targetSize.height;
|
||||
for (UIView *child in self.subviews) {
|
||||
if (child.isHidden) {
|
||||
continue;
|
||||
}
|
||||
if (!CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
|
||||
CGSize size = [child measureSize:CGSizeMake(targetSize.width, targetSize.height)];
|
||||
[child layoutSelf:size];
|
||||
DoricGravity gravity = childConfig.alignment;
|
||||
if ((gravity & LEFT) == LEFT) {
|
||||
child.left = 0;
|
||||
} else if ((gravity & RIGHT) == RIGHT) {
|
||||
@ -204,7 +235,6 @@ - (void)layout:(CGSize)targetSize {
|
||||
child.right = targetSize.width - childConfig.margin.right;
|
||||
}
|
||||
}
|
||||
|
||||
if ((gravity & TOP) == TOP) {
|
||||
child.top = 0;
|
||||
} else if ((gravity & BOTTOM) == BOTTOM) {
|
||||
@ -218,13 +248,7 @@ - (void)layout:(CGSize)targetSize {
|
||||
child.bottom = targetSize.height - childConfig.margin.bottom;
|
||||
}
|
||||
}
|
||||
|
||||
if ([child isKindOfClass:[DoricLayoutContainer class]]) {
|
||||
[(DoricLayoutContainer *) child layout:size];
|
||||
}
|
||||
}
|
||||
self.width = targetSize.width;
|
||||
self.height = targetSize.height;
|
||||
}
|
||||
@end
|
||||
|
||||
@ -232,7 +256,8 @@ @implementation DoricLinearView
|
||||
@end
|
||||
|
||||
@implementation DoricVLayoutView
|
||||
- (CGSize)sizeContent:(CGSize)size {
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
CGFloat contentWidth = 0;
|
||||
CGFloat contentHeight = 0;
|
||||
NSUInteger contentWeight = 0;
|
||||
@ -244,24 +269,11 @@ - (CGSize)sizeContent:(CGSize)size {
|
||||
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;
|
||||
CGSize childSize;
|
||||
if (CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
|
||||
childSize = [child measureSize:CGSizeMake(size.width, size.height - contentHeight)];
|
||||
} else {
|
||||
childSize = child.bounds.size;
|
||||
}
|
||||
contentWidth = MAX(contentWidth, childSize.width + childConfig.margin.left + childConfig.margin.right);
|
||||
contentHeight += childSize.height + self.space + childConfig.margin.top + childConfig.margin.bottom;
|
||||
@ -277,7 +289,9 @@ - (CGSize)sizeContent:(CGSize)size {
|
||||
return CGSizeMake(contentWidth, contentHeight);
|
||||
}
|
||||
|
||||
- (void)layout:(CGSize)targetSize {
|
||||
- (void)layoutSelf:(CGSize)targetSize {
|
||||
self.width = targetSize.width;
|
||||
self.height = targetSize.height;
|
||||
CGFloat yStart = 0;
|
||||
if ((self.gravity & TOP) == TOP) {
|
||||
yStart = 0;
|
||||
@ -291,40 +305,20 @@ - (void)layout:(CGSize)targetSize {
|
||||
if (child.isHidden) {
|
||||
continue;
|
||||
}
|
||||
if (!CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
|
||||
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;
|
||||
}
|
||||
|
||||
CGSize size = [child measureSize:CGSizeMake(targetSize.width, targetSize.height - yStart)];
|
||||
if (childConfig.weight) {
|
||||
size.height += remain / self.contentWeight * childConfig.weight;
|
||||
}
|
||||
child.width = size.width;
|
||||
child.height = size.height;
|
||||
|
||||
[child layoutSelf:size];
|
||||
DoricGravity gravity = childConfig.alignment | self.gravity;
|
||||
|
||||
if ((gravity & LEFT) == LEFT) {
|
||||
child.left = 0;
|
||||
} else if ((gravity & RIGHT) == RIGHT) {
|
||||
@ -346,18 +340,12 @@ - (void)layout:(CGSize)targetSize {
|
||||
if (childConfig.margin.bottom) {
|
||||
yStart += childConfig.margin.bottom;
|
||||
}
|
||||
if ([child isKindOfClass:[DoricLayoutContainer class]]) {
|
||||
[(DoricLayoutContainer *) child layout:size];
|
||||
}
|
||||
}
|
||||
self.width = targetSize.width;
|
||||
self.height = targetSize.height;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation DoricHLayoutView
|
||||
|
||||
- (CGSize)sizeContent:(CGSize)size {
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
CGFloat contentWidth = 0;
|
||||
CGFloat contentHeight = 0;
|
||||
NSUInteger contentWeight = 0;
|
||||
@ -369,24 +357,11 @@ - (CGSize)sizeContent:(CGSize)size {
|
||||
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 - contentWidth, size.height)];
|
||||
}
|
||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
||||
childSize.width = child.width;
|
||||
} else if (childConfig.widthSpec == DoricLayoutAtMost) {
|
||||
childSize.width = size.width - contentWidth;
|
||||
}
|
||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
||||
childSize.height = child.height;
|
||||
} else if (childConfig.heightSpec == DoricLayoutAtMost) {
|
||||
childSize.height = size.height;
|
||||
}
|
||||
if (childConfig.weight) {
|
||||
childSize.width = child.width;
|
||||
CGSize childSize;
|
||||
if (CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
|
||||
childSize = [child measureSize:CGSizeMake(size.width - contentWidth, size.height)];
|
||||
} else {
|
||||
childSize = child.bounds.size;
|
||||
}
|
||||
contentWidth += childSize.width + self.space + childConfig.margin.left + childConfig.margin.right;
|
||||
contentHeight = MAX(contentHeight, childSize.height + childConfig.margin.top + childConfig.margin.bottom);
|
||||
@ -402,7 +377,9 @@ - (CGSize)sizeContent:(CGSize)size {
|
||||
return CGSizeMake(contentWidth, contentHeight);
|
||||
}
|
||||
|
||||
- (void)layout:(CGSize)targetSize {
|
||||
- (void)layoutSelf:(CGSize)targetSize {
|
||||
self.width = targetSize.width;
|
||||
self.height = targetSize.height;
|
||||
CGFloat xStart = 0;
|
||||
if (self.contentWeight) {
|
||||
xStart = 0;
|
||||
@ -418,37 +395,20 @@ - (void)layout:(CGSize)targetSize {
|
||||
if (child.isHidden) {
|
||||
continue;
|
||||
}
|
||||
if (!CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
CGSize size = [child measureSize:CGSizeMake(targetSize.width - xStart, targetSize.height)];
|
||||
if (childConfig.weight) {
|
||||
size.width += remain / self.contentWeight * childConfig.weight;
|
||||
}
|
||||
child.width = size.width;
|
||||
child.height = size.height;
|
||||
|
||||
[child layoutSelf:size];
|
||||
|
||||
DoricGravity gravity = childConfig.alignment | self.gravity;
|
||||
if ((gravity & TOP) == TOP) {
|
||||
@ -473,45 +433,10 @@ - (void)layout:(CGSize)targetSize {
|
||||
if (childConfig.margin.right) {
|
||||
xStart += childConfig.margin.right;
|
||||
}
|
||||
if ([child isKindOfClass:[DoricLayoutContainer class]]) {
|
||||
[(DoricLayoutContainer *) child layout:size];
|
||||
}
|
||||
}
|
||||
self.width = targetSize.width;
|
||||
self.height = targetSize.height;
|
||||
}
|
||||
@end
|
||||
|
||||
static const void *kLayoutConfig = &kLayoutConfig;
|
||||
static const void *kTagString = &kTagString;
|
||||
|
||||
@implementation UIView (DoricLayoutConfig)
|
||||
@dynamic layoutConfig;
|
||||
|
||||
- (void)setLayoutConfig:(DoricLayoutConfig *)layoutConfig {
|
||||
objc_setAssociatedObject(self, kLayoutConfig, layoutConfig, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
- (DoricLayoutConfig *)layoutConfig {
|
||||
return objc_getAssociatedObject(self, kLayoutConfig);
|
||||
}
|
||||
|
||||
- (void)setTagString:(NSString *)tagString {
|
||||
objc_setAssociatedObject(self, kTagString, tagString, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
self.tag = [tagString hash];
|
||||
}
|
||||
|
||||
- (NSString *)tagString {
|
||||
return objc_getAssociatedObject(self, kTagString);
|
||||
}
|
||||
|
||||
|
||||
- (UIView *)viewWithTagString:(NSString *)tagString {
|
||||
// notice the potential hash collision
|
||||
return [self viewWithTag:[tagString hash]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
DoricVLayoutView *vLayout(NSArray <__kindof UIView *> *views) {
|
||||
DoricVLayoutView *layout = [[DoricVLayoutView alloc] initWithFrame:CGRectZero];
|
||||
|
@ -21,6 +21,7 @@
|
||||
#import "DoricListNode.h"
|
||||
#import "DoricExtensions.h"
|
||||
#import "DoricListItemNode.h"
|
||||
#import "DoricLayouts.h"
|
||||
|
||||
@interface DoricTableViewCell : UITableViewCell
|
||||
@property(nonatomic, strong) DoricListItemNode *doricListItemNode;
|
||||
@ -36,13 +37,22 @@ @implementation DoricTableView
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
if (self.subviews.count > 0) {
|
||||
CGFloat width = size.width;
|
||||
CGFloat height = 0;
|
||||
|
||||
for (UIView *child in self.subviews) {
|
||||
width = MAX(child.width, width);
|
||||
CGSize childSize = [child measureSize:size];
|
||||
width = MAX(childSize.width, width);
|
||||
height += childSize.height;
|
||||
}
|
||||
return CGSizeMake(width, size.width);
|
||||
return CGSizeMake(width, MAX(height, size.height));
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
- (void)layoutSelf:(CGSize)targetSize {
|
||||
[super layoutSelf:targetSize];
|
||||
[self reloadData];
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@ -74,9 +84,11 @@ - (UITableView *)build {
|
||||
- (void)blendView:(UITableView *)view forPropName:(NSString *)name propValue:(id)prop {
|
||||
if ([@"itemCount" isEqualToString:name]) {
|
||||
self.itemCount = [prop unsignedIntegerValue];
|
||||
[self.view reloadData];
|
||||
} else if ([@"renderItem" isEqualToString:name]) {
|
||||
[self.itemViewIds removeAllObjects];
|
||||
[self clearSubModel];
|
||||
[self.view reloadData];
|
||||
} else if ([@"batchCount" isEqualToString:name]) {
|
||||
self.batchCount = [prop unsignedIntegerValue];
|
||||
} else {
|
||||
@ -109,8 +121,8 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N
|
||||
DoricListItemNode *node = cell.doricListItemNode;
|
||||
node.viewId = model[@"id"];
|
||||
[node blend:props];
|
||||
[node.view setNeedsLayout];
|
||||
CGSize size = [node.view sizeThatFits:CGSizeMake(cell.width, cell.height)];
|
||||
CGSize size = [node.view measureSize:CGSizeMake(tableView.width, tableView.height)];
|
||||
[node.view layoutSelf:size];
|
||||
[self callItem:position height:size.height];
|
||||
return cell;
|
||||
}
|
||||
|
@ -22,5 +22,9 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "DoricSuperNode.h"
|
||||
|
||||
@interface DoricScrollerNode : DoricSuperNode<UIScrollView *>
|
||||
@interface DoricScrollView : UIScrollView
|
||||
@property(nonatomic, strong) UIView *contentView;
|
||||
@end
|
||||
|
||||
@interface DoricScrollerNode : DoricSuperNode<DoricScrollView *>
|
||||
@end
|
@ -22,27 +22,28 @@
|
||||
#import "DoricScrollerNode.h"
|
||||
#import "DoricExtensions.h"
|
||||
|
||||
@interface DoricScrollView : UIScrollView
|
||||
@end
|
||||
|
||||
@implementation DoricScrollView
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
if (self.subviews.count > 0) {
|
||||
UIView *child = self.subviews[0];
|
||||
[self setContentSize:child.frame.size];
|
||||
- (void)setContentView:(UIView *)contentView {
|
||||
if (_contentView) {
|
||||
[_contentView removeFromSuperview];
|
||||
}
|
||||
_contentView = contentView;
|
||||
[self addSubview:contentView];
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
if (self.subviews.count > 0) {
|
||||
UIView *child = self.subviews[0];
|
||||
CGSize childSize = [child sizeThatFits:size];
|
||||
return CGSizeMake(MIN(size.width, childSize.width), MIN(size.height, childSize.height));
|
||||
if (self.contentView) {
|
||||
return [self.contentView sizeThatFits:size];
|
||||
}
|
||||
return CGSizeZero;
|
||||
}
|
||||
|
||||
- (void)layoutSelf:(CGSize)targetSize {
|
||||
[super layoutSelf:targetSize];
|
||||
[self setContentSize:self.contentView.frame.size];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface DoricScrollerNode ()
|
||||
@ -51,7 +52,7 @@ @interface DoricScrollerNode ()
|
||||
@end
|
||||
|
||||
@implementation DoricScrollerNode
|
||||
- (UIScrollView *)build {
|
||||
- (DoricScrollView *)build {
|
||||
return [DoricScrollView new];
|
||||
}
|
||||
|
||||
@ -74,12 +75,11 @@ - (void)blend:(NSDictionary *)props {
|
||||
[it blend:childProps];
|
||||
}];
|
||||
} else {
|
||||
[self.childNode.view removeFromSuperview];
|
||||
self.childNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
|
||||
it.viewId = viewId;
|
||||
[it initWithSuperNode:self];
|
||||
[it blend:childProps];
|
||||
[self.view addSubview:it.view];
|
||||
self.view.contentView = it.view;
|
||||
}];
|
||||
}
|
||||
}
|
||||
@ -88,12 +88,12 @@ - (void)blend:(NSDictionary *)props {
|
||||
it.viewId = viewId;
|
||||
[it initWithSuperNode:self];
|
||||
[it blend:childProps];
|
||||
[self.view addSubview:it.view];
|
||||
self.view.contentView = it.view;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)blendView:(UIScrollView *)view forPropName:(NSString *)name propValue:(id)prop {
|
||||
- (void)blendView:(DoricScrollView *)view forPropName:(NSString *)name propValue:(id)prop {
|
||||
if ([@"content" isEqualToString:name]) {
|
||||
self.childViewId = prop;
|
||||
} else {
|
||||
@ -104,4 +104,11 @@ - (void)blendView:(UIScrollView *)view forPropName:(NSString *)name propValue:(i
|
||||
- (void)blendSubNode:(NSDictionary *)subModel {
|
||||
[self.childNode blend:subModel[@"props"]];
|
||||
}
|
||||
|
||||
- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId {
|
||||
if ([viewId isEqualToString:self.childViewId]) {
|
||||
return self.childNode;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
@end
|
@ -25,6 +25,9 @@ @interface DoricSlideItemView : DoricStackView
|
||||
@end
|
||||
|
||||
@implementation DoricSlideItemView
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation DoricSlideItemNode
|
||||
|
@ -45,12 +45,18 @@ - (CGSize)sizeThatFits:(CGSize)size {
|
||||
if (self.subviews.count > 0) {
|
||||
CGFloat height = size.height;
|
||||
for (UIView *child in self.subviews) {
|
||||
height = MAX(child.height, height);
|
||||
CGSize childSize = [child measureSize:size];
|
||||
height = MAX(childSize.height, height);
|
||||
}
|
||||
return CGSizeMake(height, size.height);
|
||||
return CGSizeMake(size.width, size.height);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
- (void)layoutSelf:(CGSize)targetSize {
|
||||
[super layoutSelf:targetSize];
|
||||
[self reloadData];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation DoricSliderNode
|
||||
@ -80,9 +86,11 @@ - (UICollectionView *)build {
|
||||
- (void)blendView:(UICollectionView *)view forPropName:(NSString *)name propValue:(id)prop {
|
||||
if ([@"itemCount" isEqualToString:name]) {
|
||||
self.itemCount = [prop unsignedIntegerValue];
|
||||
[self.view reloadData];
|
||||
} else if ([@"renderPage" isEqualToString:name]) {
|
||||
[self.itemViewIds removeAllObjects];
|
||||
[self clearSubModel];
|
||||
[self.view reloadData];
|
||||
} else if ([@"batchCount" isEqualToString:name]) {
|
||||
self.batchCount = [prop unsignedIntegerValue];
|
||||
} else {
|
||||
@ -120,7 +128,8 @@ - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collection
|
||||
DoricSlideItemNode *node = cell.doricSlideItemNode;
|
||||
node.viewId = model[@"id"];
|
||||
[node blend:props];
|
||||
[node.view setNeedsLayout];
|
||||
CGSize size = [node.view measureSize:CGSizeMake(collectionView.width, collectionView.height)];
|
||||
[node.view layoutSelf:size];
|
||||
return cell;
|
||||
}
|
||||
|
||||
|
@ -150,6 +150,8 @@ - (void)clearSubModel {
|
||||
}
|
||||
|
||||
- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId {
|
||||
NSAssert(NO, @"Should override class:%@ ,method:%@.", NSStringFromClass([self class]),
|
||||
NSStringFromSelector(_cmd));
|
||||
return nil;
|
||||
}
|
||||
- (void)requestLayout {
|
||||
|
@ -232,4 +232,18 @@ - (NSNumber *)getHeight {
|
||||
return @(self.view.height);
|
||||
}
|
||||
|
||||
- (void)setRotation:(NSNumber *)rotation {
|
||||
if (rotation.floatValue == 0) {
|
||||
self.view.transform = CGAffineTransformIdentity;
|
||||
} else {
|
||||
self.view.transform = CGAffineTransformMakeRotation(M_PI * rotation.floatValue * 2);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSNumber *)getRotation {
|
||||
float radius = atan2f((float) self.view.transform.b, (float) self.view.transform.a);
|
||||
float degree = (float) (radius / M_PI / 2);
|
||||
return @(degree);
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -32,6 +32,7 @@ extern NSString *const INJECT_REQUIRE;
|
||||
extern NSString *const INJECT_TIMER_SET;
|
||||
extern NSString *const INJECT_TIMER_CLEAR;
|
||||
extern NSString *const INJECT_BRIDGE;
|
||||
extern NSString *const INJECT_EMPTY;
|
||||
|
||||
extern NSString *const TEMPLATE_CONTEXT_CREATE;
|
||||
|
||||
|
@ -32,6 +32,7 @@
|
||||
NSString *const INJECT_TIMER_SET = @"nativeSetTimer";
|
||||
NSString *const INJECT_TIMER_CLEAR = @"nativeClearTimer";
|
||||
NSString *const INJECT_BRIDGE = @"nativeBridge";
|
||||
NSString *const INJECT_EMPTY = @"nativeEmpty";
|
||||
|
||||
NSString *const TEMPLATE_CONTEXT_CREATE = @"Reflect.apply("
|
||||
"function(doric,context,Entry,require,exports){" "\n"
|
||||
|
@ -21,6 +21,7 @@ export * from "./src/ui/scroller"
|
||||
export * from "./src/ui/widgets"
|
||||
export * from "./src/ui/panel"
|
||||
export * from "./src/ui/declarative"
|
||||
export * from "./src/ui/refreshable"
|
||||
export * from "./src/util/color"
|
||||
export * from './src/util/log'
|
||||
export * from './src/util/types'
|
||||
|
@ -57,6 +57,10 @@ export class List extends Superview implements IList {
|
||||
@Property
|
||||
batchCount = 15
|
||||
|
||||
reset() {
|
||||
this.cachedViews.clear()
|
||||
this.itemCount = 0
|
||||
}
|
||||
private getItem(itemIdx: number) {
|
||||
let view = this.cachedViews.get(`${itemIdx}`)
|
||||
if (view === undefined) {
|
||||
|
@ -31,6 +31,8 @@ export function NativeCall(target: Panel, propertyKey: string, descriptor: Prope
|
||||
|
||||
type Frame = { width: number, height: number }
|
||||
|
||||
declare function nativeEmpty(): void
|
||||
|
||||
export abstract class Panel {
|
||||
context?: any
|
||||
onCreate() { }
|
||||
@ -132,7 +134,7 @@ export abstract class Panel {
|
||||
|
||||
private hookAfterNativeCall() {
|
||||
//Here insert a native call to ensure the promise is resolved done.
|
||||
log('Check Dirty')
|
||||
nativeEmpty()
|
||||
if (this.__root__.isDirty()) {
|
||||
const model = this.__root__.toModel()
|
||||
this.nativeRender(model)
|
||||
|
74
js-framework/src/ui/refreshable.ts
Normal file
74
js-framework/src/ui/refreshable.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { View, Property, Superview, IView } from "./view";
|
||||
import { List } from "./list";
|
||||
import { Scroller } from "./scroller";
|
||||
import { BridgeContext } from "../runtime/global";
|
||||
import { layoutConfig } from "./declarative";
|
||||
|
||||
export interface IRefreshable extends IView {
|
||||
content: List | Scroller
|
||||
header?: View
|
||||
onRefresh?: () => void
|
||||
}
|
||||
|
||||
export class Refreshable extends Superview implements IRefreshable {
|
||||
|
||||
content!: List | Scroller
|
||||
|
||||
header?: View
|
||||
|
||||
@Property
|
||||
onRefresh?: () => void
|
||||
|
||||
allSubviews() {
|
||||
const ret: View[] = [this.content]
|
||||
if (this.header) {
|
||||
ret.push(this.header)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
setRefreshable(context: BridgeContext, refreshable: boolean) {
|
||||
return this.nativeChannel(context, 'setRefreshable')(refreshable)
|
||||
}
|
||||
|
||||
setRefreshing(context: BridgeContext, refreshing: boolean) {
|
||||
return this.nativeChannel(context, 'setRefreshing')(refreshing)
|
||||
}
|
||||
|
||||
isRefreshable(context: BridgeContext) {
|
||||
return this.nativeChannel(context, 'isRefreshable')() as Promise<boolean>
|
||||
}
|
||||
|
||||
isRefreshing(context: BridgeContext) {
|
||||
return this.nativeChannel(context, 'isRefreshing')() as Promise<boolean>
|
||||
}
|
||||
|
||||
toModel() {
|
||||
this.dirtyProps.content = this.content.viewId
|
||||
this.dirtyProps.header = (this.header || {}).viewId
|
||||
return super.toModel()
|
||||
}
|
||||
}
|
||||
|
||||
export function refreshable(config: IRefreshable) {
|
||||
const ret = new Refreshable
|
||||
ret.layoutConfig = layoutConfig().wrap()
|
||||
for (let key in config) {
|
||||
Reflect.set(ret, key, Reflect.get(config, key, config), ret)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
export interface IPullable {
|
||||
startAnimation(): void
|
||||
stopAnimation(): void
|
||||
setProgressRotation(rotation: number): void
|
||||
}
|
||||
|
||||
|
||||
export function pullable(context: BridgeContext, v: View, config: IPullable) {
|
||||
Reflect.set(v, 'startAnimation', config.startAnimation)
|
||||
Reflect.set(v, 'stopAnimation', config.stopAnimation)
|
||||
Reflect.set(v, 'setProgressRotation', config.setProgressRotation)
|
||||
return v
|
||||
}
|
@ -18,6 +18,7 @@ import { Modeling, Model, obj2Model } from "../util/types";
|
||||
import { uniqueId } from "../util/uniqueId";
|
||||
import { Gravity } from "../util/gravity";
|
||||
import { loge } from "../util/log";
|
||||
import { BridgeContext } from "../runtime/global";
|
||||
|
||||
export enum LayoutSpec {
|
||||
EXACTLY = 0,
|
||||
@ -266,7 +267,7 @@ export abstract class View implements Modeling, IView {
|
||||
|
||||
nativeChannel(context: any, name: string) {
|
||||
let thisView: View | undefined = this
|
||||
return function (...args: any) {
|
||||
return function (args: any = undefined) {
|
||||
const func = context.shader.command
|
||||
const viewIds = []
|
||||
while (thisView != undefined) {
|
||||
@ -281,6 +282,29 @@ export abstract class View implements Modeling, IView {
|
||||
return Reflect.apply(func, undefined, [params]) as Promise<any>
|
||||
}
|
||||
}
|
||||
|
||||
getWidth(context: BridgeContext) {
|
||||
return this.nativeChannel(context, 'getWidth')() as Promise<number>
|
||||
}
|
||||
|
||||
getHeight(context: BridgeContext) {
|
||||
return this.nativeChannel(context, 'getHeight')() as Promise<number>
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param rotation [0..1]
|
||||
*/
|
||||
setRotation(context: BridgeContext, rotation: number) {
|
||||
return this.nativeChannel(context, 'setRotation')(rotation)
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @return rotation [0..1]
|
||||
*/
|
||||
getRotation(context: BridgeContext) {
|
||||
return this.nativeChannel(context, 'getRotation')() as Promise<number>
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Superview extends View {
|
||||
@ -316,6 +340,7 @@ export abstract class Superview extends View {
|
||||
toModel() {
|
||||
const subviews = []
|
||||
for (let v of this.allSubviews()) {
|
||||
v.superview = this
|
||||
if (v.isDirty()) {
|
||||
subviews.push(v.toModel())
|
||||
}
|
||||
@ -341,7 +366,6 @@ export abstract class Group extends Superview {
|
||||
}
|
||||
|
||||
addChild(view: View) {
|
||||
view.superview = this
|
||||
this.children.push(view)
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
"lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
|
Reference in New Issue
Block a user