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:glide:4.10.0'
|
||||||
implementation 'com.github.bumptech.glide:annotations:4.10.0'
|
implementation 'com.github.bumptech.glide:annotations:4.10.0'
|
||||||
implementation 'com.github.penfeizhou.android.animation:glide-plugin:1.3.1'
|
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'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
|
||||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-4'
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-4'
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
package="pub.doric.demo">
|
package="pub.doric.demo">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MyApplication"
|
android:name=".MyApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@ -12,6 +13,7 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
|
<activity android:name=".PullableActivity"></activity>
|
||||||
<activity android:name=".MainActivity">
|
<activity android:name=".MainActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package pub.doric.demo;
|
package pub.doric.demo;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.graphics.Color;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
@ -34,6 +35,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import pub.doric.DoricActivity;
|
import pub.doric.DoricActivity;
|
||||||
import pub.doric.devkit.ui.DemoDebugActivity;
|
import pub.doric.devkit.ui.DemoDebugActivity;
|
||||||
|
import pub.doric.refresh.DoricSwipeLayout;
|
||||||
import pub.doric.utils.DoricUtils;
|
import pub.doric.utils.DoricUtils;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
@ -43,11 +45,25 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
setContentView(R.layout.activity_main);
|
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 recyclerView = findViewById(R.id.root);
|
||||||
|
recyclerView.setBackgroundColor(Color.WHITE);
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||||
try {
|
try {
|
||||||
String[] demos = getAssets().list("demo");
|
String[] demos = getAssets().list("demo");
|
||||||
List<String> ret = new ArrayList<>();
|
List<String> ret = new ArrayList<>();
|
||||||
|
ret.add("Test");
|
||||||
for (String str : demos) {
|
for (String str : demos) {
|
||||||
if (str.endsWith("js")) {
|
if (str.endsWith("js")) {
|
||||||
ret.add(str);
|
ret.add(str);
|
||||||
@ -91,6 +107,11 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
tv.setOnClickListener(new View.OnClickListener() {
|
tv.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
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")) {
|
if (data[position].contains("NavigatorDemo")) {
|
||||||
Intent intent = new Intent(tv.getContext(), DoricActivity.class);
|
Intent intent = new Intent(tv.getContext(), DoricActivity.class);
|
||||||
intent.putExtra("scheme", "assets://demo/" + data[position]);
|
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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<pub.doric.refresh.DoricSwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/root"
|
android:id="@+id/swipe_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
tools:context=".MainActivity" />
|
|
||||||
|
<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.NetworkPlugin;
|
||||||
import pub.doric.plugin.ShaderPlugin;
|
import pub.doric.plugin.ShaderPlugin;
|
||||||
import pub.doric.plugin.StoragePlugin;
|
import pub.doric.plugin.StoragePlugin;
|
||||||
|
import pub.doric.refresh.RefreshableNode;
|
||||||
import pub.doric.shader.HLayoutNode;
|
import pub.doric.shader.HLayoutNode;
|
||||||
import pub.doric.shader.ImageNode;
|
import pub.doric.shader.ImageNode;
|
||||||
import pub.doric.shader.ScrollerNode;
|
import pub.doric.shader.ScrollerNode;
|
||||||
@ -96,6 +97,7 @@ public class DoricRegistry {
|
|||||||
this.registerViewNode(ScrollerNode.class);
|
this.registerViewNode(ScrollerNode.class);
|
||||||
this.registerViewNode(SliderNode.class);
|
this.registerViewNode(SliderNode.class);
|
||||||
this.registerViewNode(SlideItemNode.class);
|
this.registerViewNode(SlideItemNode.class);
|
||||||
|
this.registerViewNode(RefreshableNode.class);
|
||||||
initRegistry(this);
|
initRegistry(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +96,12 @@ public class DoricJSEngine implements Handler.Callback, DoricTimerExtension.Time
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
mDoricJSE.injectGlobalJSFunction(DoricConstant.INJECT_EMPTY, new JavaFunction() {
|
||||||
|
@Override
|
||||||
|
public JavaValue exec(JSDecoder[] args) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
mDoricJSE.injectGlobalJSFunction(DoricConstant.INJECT_REQUIRE, new JavaFunction() {
|
mDoricJSE.injectGlobalJSFunction(DoricConstant.INJECT_REQUIRE, new JavaFunction() {
|
||||||
@Override
|
@Override
|
||||||
public JavaValue exec(JSDecoder[] args) {
|
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.DoricContext;
|
||||||
import pub.doric.extension.bridge.DoricPlugin;
|
import pub.doric.extension.bridge.DoricPlugin;
|
||||||
|
import pub.doric.utils.DoricUtils;
|
||||||
|
|
||||||
import com.github.pengfeizhou.jscore.JSONBuilder;
|
import com.github.pengfeizhou.jscore.JSONBuilder;
|
||||||
import com.github.pengfeizhou.jscore.JSValue;
|
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) {
|
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
|
||||||
if (!TextUtils.isEmpty(loadCallbackId)) {
|
if (!TextUtils.isEmpty(loadCallbackId)) {
|
||||||
callJSResponse(loadCallbackId, new JSONBuilder()
|
callJSResponse(loadCallbackId, new JSONBuilder()
|
||||||
.put("width", resource.getIntrinsicWidth())
|
.put("width", DoricUtils.px2dp(resource.getIntrinsicWidth()))
|
||||||
.put("height", resource.getIntrinsicHeight())
|
.put("height", DoricUtils.px2dp(resource.getIntrinsicHeight()))
|
||||||
.toJSONObject());
|
.toJSONObject());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -257,4 +257,21 @@ public abstract class ViewNode<T extends View> extends DoricContextHolder {
|
|||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
return mView.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_SET = "nativeSetTimer";
|
||||||
public static final String INJECT_TIMER_CLEAR = "nativeClearTimer";
|
public static final String INJECT_TIMER_CLEAR = "nativeClearTimer";
|
||||||
public static final String INJECT_BRIDGE = "nativeBridge";
|
public static final String INJECT_BRIDGE = "nativeBridge";
|
||||||
|
public static final String INJECT_EMPTY = "nativeEmpty";
|
||||||
|
|
||||||
public static final String TEMPLATE_CONTEXT_CREATE = "Reflect.apply(" +
|
public static final String TEMPLATE_CONTEXT_CREATE = "Reflect.apply(" +
|
||||||
"function(doric,context,Entry,require,exports){" + "\n" +
|
"function(doric,context,Entry,require,exports){" + "\n" +
|
||||||
|
@ -81,6 +81,8 @@ public class DoricUtils {
|
|||||||
return new JavaValue((Integer) arg);
|
return new JavaValue((Integer) arg);
|
||||||
} else if (arg instanceof Long) {
|
} else if (arg instanceof Long) {
|
||||||
return new JavaValue((Long) arg);
|
return new JavaValue((Long) arg);
|
||||||
|
} else if (arg instanceof Float) {
|
||||||
|
return new JavaValue((Float) arg);
|
||||||
} else if (arg instanceof Double) {
|
} else if (arg instanceof Double) {
|
||||||
return new JavaValue((Double) arg);
|
return new JavaValue((Double) arg);
|
||||||
} else if (arg instanceof Boolean) {
|
} else if (arg instanceof Boolean) {
|
||||||
|
@ -12,4 +12,5 @@ export default [
|
|||||||
'src/StorageDemo',
|
'src/StorageDemo',
|
||||||
'src/NavigatorDemo',
|
'src/NavigatorDemo',
|
||||||
'src/NavbarDemo',
|
'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";
|
import { O_TRUNC } from "constants";
|
||||||
|
|
||||||
const colors = [
|
const colors = [
|
||||||
@ -453,11 +453,7 @@ class LayoutDemo extends Panel {
|
|||||||
it.space = 20
|
it.space = 20
|
||||||
}),
|
}),
|
||||||
).also(it => {
|
).also(it => {
|
||||||
it.layoutConfig = {
|
it.layoutConfig = layoutConfig().atmost()
|
||||||
widthSpec: LayoutSpec.WRAP_CONTENT,
|
|
||||||
heightSpec: LayoutSpec.WRAP_CONTENT,
|
|
||||||
alignment: gravity().centerX(),
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.in(rootView)
|
.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";
|
import { Group, Panel, List, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout, Gravity, hlayout, Text, refreshable, Refreshable, ListItem } from "doric";
|
||||||
const colors = [
|
import { rotatedArrow, colors } from "./utils";
|
||||||
"#f0932b",
|
|
||||||
"#eb4d4b",
|
|
||||||
"#6ab04c",
|
|
||||||
"#e056fd",
|
|
||||||
"#686de0",
|
|
||||||
"#30336b",
|
|
||||||
]
|
|
||||||
@Entry
|
@Entry
|
||||||
class ListPanel extends Panel {
|
class ListPanel extends Panel {
|
||||||
build(rootView: Group): void {
|
build(rootView: Group): void {
|
||||||
|
let refreshView: Refreshable
|
||||||
|
let offset = Math.ceil(Math.random() * colors.length)
|
||||||
vlayout([
|
vlayout([
|
||||||
text({
|
text({
|
||||||
text: "ListDemo",
|
text: "ListDemo",
|
||||||
@ -23,76 +18,90 @@ class ListPanel extends Panel {
|
|||||||
textAlignment: gravity().center(),
|
textAlignment: gravity().center(),
|
||||||
height: 50,
|
height: 50,
|
||||||
}),
|
}),
|
||||||
list({
|
refreshView = refreshable({
|
||||||
itemCount: 1000,
|
onRefresh: () => {
|
||||||
renderItem: (idx: number) => {
|
refreshView.setRefreshing(context, false).then(() => {
|
||||||
let counter!: Text
|
(refreshView.content as List).also(it => {
|
||||||
return listItem(
|
it.reset()
|
||||||
hlayout([
|
offset = Math.ceil(Math.random() * colors.length)
|
||||||
text({
|
it.itemCount = 40
|
||||||
layoutConfig: {
|
it.renderItem = (idx: number) => {
|
||||||
widthSpec: LayoutSpec.WRAP_CONTENT,
|
let counter!: Text
|
||||||
heightSpec: LayoutSpec.EXACTLY,
|
return listItem(
|
||||||
alignment: gravity().center(),
|
hlayout([
|
||||||
},
|
text({
|
||||||
text: `Cell At Line ${idx}`,
|
layoutConfig: {
|
||||||
textAlignment: gravity().center(),
|
widthSpec: LayoutSpec.WRAP_CONTENT,
|
||||||
textColor: Color.parse("#ffffff"),
|
heightSpec: LayoutSpec.EXACTLY,
|
||||||
textSize: 20,
|
alignment: gravity().center(),
|
||||||
height: 50,
|
},
|
||||||
}),
|
text: `Cell At Line ${idx}`,
|
||||||
text({
|
textAlignment: gravity().center(),
|
||||||
textColor: Color.parse("#ffffff"),
|
textColor: Color.parse("#ffffff"),
|
||||||
textSize: 20,
|
textSize: 20,
|
||||||
text: "",
|
height: 50,
|
||||||
}).also(it => {
|
}),
|
||||||
counter = it
|
text({
|
||||||
it.layoutConfig = {
|
textColor: Color.parse("#ffffff"),
|
||||||
widthSpec: LayoutSpec.WRAP_CONTENT,
|
textSize: 20,
|
||||||
heightSpec: LayoutSpec.WRAP_CONTENT,
|
text: "",
|
||||||
margin: {
|
}).also(it => {
|
||||||
left: 10,
|
counter = it
|
||||||
|
it.layoutConfig = {
|
||||||
|
widthSpec: LayoutSpec.WRAP_CONTENT,
|
||||||
|
heightSpec: LayoutSpec.WRAP_CONTENT,
|
||||||
|
margin: {
|
||||||
|
left: 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]).also(it => {
|
||||||
|
it.layoutConfig = {
|
||||||
|
widthSpec: LayoutSpec.AT_MOST,
|
||||||
|
heightSpec: LayoutSpec.WRAP_CONTENT,
|
||||||
|
margin: {
|
||||||
|
bottom: 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.gravity = gravity().center()
|
||||||
|
it.bgColor = colors[(idx + offset) % colors.length]
|
||||||
|
let clicked = 0
|
||||||
|
it.onClick = () => {
|
||||||
|
counter.text = `Item Clicked ${++clicked}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).also(it => {
|
||||||
|
it.layoutConfig = {
|
||||||
|
widthSpec: LayoutSpec.AT_MOST,
|
||||||
|
heightSpec: LayoutSpec.WRAP_CONTENT,
|
||||||
}
|
}
|
||||||
}
|
it.onClick = () => {
|
||||||
})
|
log(`Click item at ${idx}`)
|
||||||
]).also(it => {
|
it.height += 10
|
||||||
it.layoutConfig = {
|
it.nativeChannel(context, "getWidth")().then(
|
||||||
widthSpec: LayoutSpec.AT_MOST,
|
resolve => {
|
||||||
heightSpec: LayoutSpec.WRAP_CONTENT,
|
log(`resolve,${resolve}`)
|
||||||
margin: {
|
},
|
||||||
bottom: 2,
|
reject => {
|
||||||
}
|
log(`reject,${reject}`)
|
||||||
}
|
})
|
||||||
it.gravity = gravity().center()
|
}
|
||||||
it.bgColor = Color.parse(colors[idx % colors.length])
|
})
|
||||||
let clicked = 0
|
|
||||||
it.onClick = () => {
|
|
||||||
counter.text = `Item Clicked ${++clicked}`
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
).also(it => {
|
|
||||||
it.layoutConfig = {
|
|
||||||
widthSpec: LayoutSpec.AT_MOST,
|
|
||||||
heightSpec: LayoutSpec.WRAP_CONTENT,
|
|
||||||
}
|
|
||||||
it.onClick = () => {
|
|
||||||
log(`Click item at ${idx}`)
|
|
||||||
it.height += 10
|
|
||||||
it.nativeChannel(context, "getWidth")().then(
|
|
||||||
resolve => {
|
|
||||||
log(`resolve,${resolve}`)
|
|
||||||
},
|
|
||||||
reject => {
|
|
||||||
log(`reject,${reject}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
layoutConfig: {
|
header: rotatedArrow(context),
|
||||||
widthSpec: LayoutSpec.AT_MOST,
|
content: list({
|
||||||
heightSpec: LayoutSpec.AT_MOST,
|
itemCount: 0,
|
||||||
},
|
renderItem: () => new ListItem,
|
||||||
|
layoutConfig: {
|
||||||
|
widthSpec: LayoutSpec.AT_MOST,
|
||||||
|
heightSpec: LayoutSpec.AT_MOST,
|
||||||
|
},
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
]).also(it => {
|
]).also(it => {
|
||||||
it.layoutConfig = {
|
it.layoutConfig = {
|
||||||
widthSpec: LayoutSpec.AT_MOST,
|
widthSpec: LayoutSpec.AT_MOST,
|
||||||
@ -100,5 +109,6 @@ class ListPanel extends Panel {
|
|||||||
}
|
}
|
||||||
it.bgColor = Color.WHITE
|
it.bgColor = Color.WHITE
|
||||||
}).in(rootView)
|
}).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";
|
import { Group, Panel, List, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout, Gravity, hlayout, scroller, layoutConfig } from "doric";
|
||||||
const colors = [
|
import { label } from "./utils";
|
||||||
"#f0932b",
|
|
||||||
"#eb4d4b",
|
|
||||||
"#6ab04c",
|
|
||||||
"#e056fd",
|
|
||||||
"#686de0",
|
|
||||||
"#30336b",
|
|
||||||
]
|
|
||||||
@Entry
|
@Entry
|
||||||
class ScrollerPanel extends Panel {
|
class ScrollerPanel extends Panel {
|
||||||
build(rootView: Group): void {
|
build(rootView: Group): void {
|
||||||
rootView.addChild(scroller(vlayout(
|
scroller(
|
||||||
[
|
vlayout([
|
||||||
// ...[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5].map(e => text({
|
scroller(
|
||||||
// text: colors[e % colors.length],
|
vlayout(new Array(100).fill(1).map(e => label('Scroll Content')))
|
||||||
// textColor: Color.parse('#ffffff'),
|
).apply({
|
||||||
// textSize: 20,
|
layoutConfig: layoutConfig().exactly(),
|
||||||
// bgColor: Color.parse(colors[e % colors.length]),
|
width: 300,
|
||||||
// layoutConfig: {
|
height: 500,
|
||||||
// widthSpec: LayoutSpec.EXACTLY,
|
bgColor: Color.RED,
|
||||||
// heightSpec: LayoutSpec.EXACTLY,
|
}),
|
||||||
// },
|
scroller(
|
||||||
// width: 200,
|
vlayout(new Array(100).fill(1).map(e => label('Scroll Content')))
|
||||||
// height: 50,
|
).apply({
|
||||||
// })),
|
layoutConfig: layoutConfig().exactly(),
|
||||||
...[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5].map(i => hlayout([
|
width: 300,
|
||||||
...[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5].map(j => text({
|
height: 500,
|
||||||
text: colors[(i + j) % colors.length],
|
bgColor: Color.BLUE,
|
||||||
textColor: Color.parse('#ffffff'),
|
})
|
||||||
textSize: 20,
|
])
|
||||||
bgColor: Color.parse(colors[(i + j) % colors.length]),
|
)
|
||||||
layoutConfig: {
|
.apply({
|
||||||
widthSpec: LayoutSpec.EXACTLY,
|
layoutConfig: layoutConfig().atmost().h(LayoutSpec.EXACTLY),
|
||||||
heightSpec: LayoutSpec.EXACTLY,
|
height: 500,
|
||||||
},
|
bgColor: Color.YELLOW,
|
||||||
width: 80,
|
})
|
||||||
height: 50,
|
.in(rootView)
|
||||||
})),
|
|
||||||
]).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)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
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 */
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
"target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
"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'. */
|
"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. */
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
// "checkJs": true, /* Report errors in .js files. */
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
// "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.public_header_files = 'Pod/Classes/**/*.h'
|
||||||
# s.frameworks = 'UIKit', 'MapKit'
|
# s.frameworks = 'UIKit', 'MapKit'
|
||||||
# s.dependency 'AFNetworking', '~> 2.3'
|
# s.dependency 'AFNetworking', '~> 2.3'
|
||||||
s.dependency 'SDWebImage', '~> 4.4.7'
|
# s.dependency 'SDWebImage', '~> 5.0'
|
||||||
s.dependency 'SDWebImage/WebP'
|
s.dependency 'YYWebImage', '~>1.0.5'
|
||||||
|
s.dependency 'YYImage/WebP'
|
||||||
s.dependency 'SocketRocket', '~> 0.5.1'
|
s.dependency 'SocketRocket', '~> 0.5.1'
|
||||||
s.dependency 'GCDWebServer', '~> 3.0'
|
s.dependency 'GCDWebServer', '~> 3.0'
|
||||||
s.dependency 'YYCache', '~> 1.0.4'
|
s.dependency 'YYCache', '~> 1.0.4'
|
||||||
|
@ -1,30 +1,23 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- Doric (0.1.0):
|
- Doric (0.1.0):
|
||||||
- GCDWebServer (~> 3.0)
|
- GCDWebServer (~> 3.0)
|
||||||
- SDWebImage (~> 4.4.7)
|
|
||||||
- SDWebImage/WebP
|
|
||||||
- SocketRocket (~> 0.5.1)
|
- SocketRocket (~> 0.5.1)
|
||||||
- YYCache (~> 1.0.4)
|
- YYCache (~> 1.0.4)
|
||||||
|
- YYImage/WebP
|
||||||
|
- YYWebImage
|
||||||
- GCDWebServer (3.5.3):
|
- GCDWebServer (3.5.3):
|
||||||
- GCDWebServer/Core (= 3.5.3)
|
- GCDWebServer/Core (= 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)
|
- SocketRocket (0.5.1)
|
||||||
- YYCache (1.0.4)
|
- 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:
|
DEPENDENCIES:
|
||||||
- Doric (from `../`)
|
- Doric (from `../`)
|
||||||
@ -32,22 +25,22 @@ DEPENDENCIES:
|
|||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
https://github.com/cocoapods/specs.git:
|
https://github.com/cocoapods/specs.git:
|
||||||
- GCDWebServer
|
- GCDWebServer
|
||||||
- libwebp
|
|
||||||
- SDWebImage
|
|
||||||
- SocketRocket
|
- SocketRocket
|
||||||
- YYCache
|
- YYCache
|
||||||
|
- YYImage
|
||||||
|
- YYWebImage
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
Doric:
|
Doric:
|
||||||
:path: "../"
|
:path: "../"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Doric: c71287d68afeeb79bfd3c680ed2dd3b90d515c12
|
Doric: e73b17b0e46198994f5c3d8af49f26fd9f49df09
|
||||||
GCDWebServer: c0ab22c73e1b84f358d1e2f74bf6afd1c60829f2
|
GCDWebServer: c0ab22c73e1b84f358d1e2f74bf6afd1c60829f2
|
||||||
libwebp: 057912d6d0abfb6357d8bb05c0ea470301f5d61e
|
|
||||||
SDWebImage: c10d14a8883ebd89664f02a422006f66a85c0c5d
|
|
||||||
SocketRocket: d57c7159b83c3c6655745cd15302aa24b6bae531
|
SocketRocket: d57c7159b83c3c6655745cd15302aa24b6bae531
|
||||||
YYCache: 8105b6638f5e849296c71f331ff83891a4942952
|
YYCache: 8105b6638f5e849296c71f331ff83891a4942952
|
||||||
|
YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54
|
||||||
|
YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928
|
||||||
|
|
||||||
PODFILE CHECKSUM: 012563d71439e7e33e976dca3b59664ed56cee39
|
PODFILE CHECKSUM: 012563d71439e7e33e976dca3b59664ed56cee39
|
||||||
|
|
||||||
|
@ -24,4 +24,5 @@
|
|||||||
#import "DoricJSLoaderManager.h"
|
#import "DoricJSLoaderManager.h"
|
||||||
#import "DoricNavigatorDelegate.h"
|
#import "DoricNavigatorDelegate.h"
|
||||||
#import "DoricNavBarDelegate.h"
|
#import "DoricNavBarDelegate.h"
|
||||||
#import "DoricViewController.h"
|
#import "DoricViewController.h"
|
||||||
|
#import "DoricPromise.h"
|
@ -37,6 +37,7 @@
|
|||||||
#import "DoricStoragePlugin.h"
|
#import "DoricStoragePlugin.h"
|
||||||
#import "DoricNavigatorPlugin.h"
|
#import "DoricNavigatorPlugin.h"
|
||||||
#import "DoricNavBarPlugin.h"
|
#import "DoricNavBarPlugin.h"
|
||||||
|
#import "DoricRefreshableNode.h"
|
||||||
|
|
||||||
@interface DoricRegistry ()
|
@interface DoricRegistry ()
|
||||||
|
|
||||||
@ -76,6 +77,7 @@ - (void)innerRegister {
|
|||||||
[self registerViewNode:DoricScrollerNode.class withName:@"Scroller"];
|
[self registerViewNode:DoricScrollerNode.class withName:@"Scroller"];
|
||||||
[self registerViewNode:DoricSliderNode.class withName:@"Slider"];
|
[self registerViewNode:DoricSliderNode.class withName:@"Slider"];
|
||||||
[self registerViewNode:DoricSlideItemNode.class withName:@"SlideItem"];
|
[self registerViewNode:DoricSlideItemNode.class withName:@"SlideItem"];
|
||||||
|
[self registerViewNode:DoricRefreshableNode.class withName:@"Refreshable"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name {
|
- (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name {
|
||||||
|
@ -59,7 +59,9 @@ - (void)initJSExecutor {
|
|||||||
[self.jsExecutor injectGlobalJSObject:INJECT_LOG obj:^(NSString *type, NSString *message) {
|
[self.jsExecutor injectGlobalJSObject:INJECT_LOG obj:^(NSString *type, NSString *message) {
|
||||||
DoricLog(@"JS:%@", message);
|
DoricLog(@"JS:%@", message);
|
||||||
}];
|
}];
|
||||||
|
[self.jsExecutor injectGlobalJSObject:INJECT_EMPTY obj:^() {
|
||||||
|
|
||||||
|
}];
|
||||||
[self.jsExecutor injectGlobalJSObject:INJECT_REQUIRE obj:^(NSString *name) {
|
[self.jsExecutor injectGlobalJSObject:INJECT_REQUIRE obj:^(NSString *name) {
|
||||||
__strong typeof(_self) self = _self;
|
__strong typeof(_self) self = _self;
|
||||||
if (!self) return NO;
|
if (!self) return NO;
|
||||||
|
@ -25,8 +25,6 @@
|
|||||||
#import "DoricUtil.h"
|
#import "DoricUtil.h"
|
||||||
#import "Doric.h"
|
#import "Doric.h"
|
||||||
|
|
||||||
#import <JavaScriptCore/JavaScriptCore.h>
|
|
||||||
|
|
||||||
#import <objc/runtime.h>
|
#import <objc/runtime.h>
|
||||||
|
|
||||||
@implementation DoricShaderPlugin
|
@implementation DoricShaderPlugin
|
||||||
@ -110,9 +108,13 @@ - (id)findClass:(Class)clz target:(id)target method:(NSString *)name promise:(Do
|
|||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
void *retValue;
|
void *retValue;
|
||||||
block();
|
block();
|
||||||
[invocation getReturnValue:&retValue];
|
const char *retType = methodSignature.methodReturnType;
|
||||||
id returnValue = (__bridge id) retValue;
|
if (!strcmp(retType, @encode(void))) {
|
||||||
[promise resolve:returnValue];
|
} else {
|
||||||
|
[invocation getReturnValue:&retValue];
|
||||||
|
id returnValue = (__bridge id) retValue;
|
||||||
|
[promise resolve:returnValue];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return ret;
|
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 "DoricImageNode.h"
|
||||||
#import "Doric.h"
|
#import "Doric.h"
|
||||||
#import <SDWebImage/UIImageView+WebCache.h>
|
#import "YYWebImage.h"
|
||||||
|
|
||||||
@interface DoricImageNode ()
|
@interface DoricImageNode ()
|
||||||
@property(nonatomic, copy) NSString *loadCallbackId;
|
@property(nonatomic, copy) NSString *loadCallbackId;
|
||||||
@ -31,7 +31,7 @@ @interface DoricImageNode ()
|
|||||||
@implementation DoricImageNode
|
@implementation DoricImageNode
|
||||||
|
|
||||||
- (UIImageView *)build {
|
- (UIImageView *)build {
|
||||||
return [[UIImageView new] also:^(UIImageView *it) {
|
return [[YYAnimatedImageView new] also:^(UIImageView *it) {
|
||||||
it.clipsToBounds = YES;
|
it.clipsToBounds = YES;
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ - (UIImageView *)build {
|
|||||||
- (void)blendView:(UIImageView *)view forPropName:(NSString *)name propValue:(id)prop {
|
- (void)blendView:(UIImageView *)view forPropName:(NSString *)name propValue:(id)prop {
|
||||||
if ([@"imageUrl" isEqualToString:name]) {
|
if ([@"imageUrl" isEqualToString:name]) {
|
||||||
__weak typeof(self) _self = self;
|
__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;
|
__strong typeof(_self) self = _self;
|
||||||
if (error) {
|
if (error) {
|
||||||
if (self.loadCallbackId.length > 0) {
|
if (self.loadCallbackId.length > 0) {
|
||||||
@ -53,7 +53,6 @@ - (void)blendView:(UIImageView *)view forPropName:(NSString *)name propValue:(id
|
|||||||
}
|
}
|
||||||
[self requestLayout];
|
[self requestLayout];
|
||||||
}
|
}
|
||||||
|
|
||||||
}];
|
}];
|
||||||
} else if ([@"scaleType" isEqualToString:name]) {
|
} else if ([@"scaleType" isEqualToString:name]) {
|
||||||
switch ([prop integerValue]) {
|
switch ([prop integerValue]) {
|
||||||
|
@ -19,14 +19,7 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
typedef UIEdgeInsets DoricMargin;
|
||||||
struct DoricMargin {
|
|
||||||
CGFloat left;
|
|
||||||
CGFloat right;
|
|
||||||
CGFloat top;
|
|
||||||
CGFloat bottom;
|
|
||||||
};
|
|
||||||
typedef struct DoricMargin DoricMargin;
|
|
||||||
|
|
||||||
DoricMargin DoricMarginMake(CGFloat left, CGFloat top, CGFloat right, CGFloat bottom);
|
DoricMargin DoricMarginMake(CGFloat left, CGFloat top, CGFloat right, CGFloat bottom);
|
||||||
|
|
||||||
@ -68,17 +61,13 @@ typedef NS_ENUM(NSInteger, DoricGravity) {
|
|||||||
|
|
||||||
|
|
||||||
@interface DoricLayoutContainer : UIView
|
@interface DoricLayoutContainer : UIView
|
||||||
@property(nonatomic, assign) DoricGravity gravity;
|
|
||||||
|
|
||||||
- (void)layout:(CGSize)targetSize;
|
|
||||||
|
|
||||||
- (CGSize)sizeContent:(CGSize)size;
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface DoricStackView : DoricLayoutContainer
|
@interface DoricStackView : DoricLayoutContainer
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface DoricLinearView : DoricLayoutContainer
|
@interface DoricLinearView : DoricLayoutContainer
|
||||||
|
@property(nonatomic, assign) DoricGravity gravity;
|
||||||
@property(nonatomic, assign) CGFloat space;
|
@property(nonatomic, assign) CGFloat space;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@ -91,7 +80,20 @@ typedef NS_ENUM(NSInteger, DoricGravity) {
|
|||||||
|
|
||||||
@interface UIView (DoricLayoutConfig)
|
@interface UIView (DoricLayoutConfig)
|
||||||
@property(nonatomic, strong) DoricLayoutConfig *layoutConfig;
|
@property(nonatomic, strong) DoricLayoutConfig *layoutConfig;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface UIView (DoricTag)
|
||||||
@property(nonatomic, copy) NSString *tagString;
|
@property(nonatomic, copy) NSString *tagString;
|
||||||
|
|
||||||
- (UIView *)viewWithTagString:(NSString *)tagString;
|
- (UIView *)viewWithTagString:(NSString *)tagString;
|
||||||
@end
|
@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 "DoricLayouts.h"
|
||||||
#import <objc/runtime.h>
|
#import <objc/runtime.h>
|
||||||
|
#import <Doric/DoricLayouts.h>
|
||||||
#import "UIView+Doric.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 DoricMarginMake(CGFloat left, CGFloat top, CGFloat right, CGFloat bottom) {
|
||||||
DoricMargin margin;
|
DoricMargin margin;
|
||||||
margin.left = left;
|
margin.left = left;
|
||||||
@ -65,63 +164,23 @@ @interface DoricLayoutContainer ()
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation DoricLayoutContainer
|
@implementation DoricLayoutContainer
|
||||||
|
- (void)setNeedsLayout {
|
||||||
|
[super setNeedsLayout];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)layoutSubviews {
|
- (void)layoutSubviews {
|
||||||
if ([self.superview isKindOfClass:[DoricLayoutContainer class]]) {
|
[super layoutSubviews];
|
||||||
[self.superview layoutSubviews];
|
[self doricLayoutSubviews];
|
||||||
} 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;
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
@interface DoricStackView ()
|
@interface DoricStackView ()
|
||||||
|
|
||||||
@property(nonatomic, assign) CGFloat contentWidth;
|
|
||||||
@property(nonatomic, assign) CGFloat contentHeight;
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation DoricStackView
|
@implementation DoricStackView
|
||||||
|
|
||||||
- (CGSize)sizeContent:(CGSize)size {
|
- (CGSize)sizeThatFits:(CGSize)size {
|
||||||
CGFloat contentWidth = 0;
|
CGFloat contentWidth = 0;
|
||||||
CGFloat contentHeight = 0;
|
CGFloat contentHeight = 0;
|
||||||
for (UIView *child in self.subviews) {
|
for (UIView *child in self.subviews) {
|
||||||
@ -132,24 +191,11 @@ - (CGSize)sizeContent:(CGSize)size {
|
|||||||
if (!childConfig) {
|
if (!childConfig) {
|
||||||
childConfig = [DoricLayoutConfig new];
|
childConfig = [DoricLayoutConfig new];
|
||||||
}
|
}
|
||||||
CGSize childSize = CGSizeMake(child.width, child.height);
|
CGSize childSize;
|
||||||
if ([child isKindOfClass:[DoricLayoutContainer class]]
|
if (CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
|
||||||
|| childConfig.widthSpec == DoricLayoutWrapContent
|
childSize = [child measureSize:CGSizeMake(size.width, size.height)];
|
||||||
|| childConfig.heightSpec == DoricLayoutWrapContent) {
|
} else {
|
||||||
childSize = [child sizeThatFits:CGSizeMake(size.width, size.height - contentHeight)];
|
childSize = child.bounds.size;
|
||||||
}
|
|
||||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
|
||||||
childSize.width = child.width;
|
|
||||||
} else if (childConfig.widthSpec == DoricLayoutAtMost) {
|
|
||||||
childSize.width = size.width;
|
|
||||||
}
|
|
||||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
|
||||||
childSize.height = child.height;
|
|
||||||
} else if (childConfig.heightSpec == DoricLayoutAtMost) {
|
|
||||||
childSize.height = size.height - contentHeight;
|
|
||||||
}
|
|
||||||
if (childConfig.weight) {
|
|
||||||
childSize.height = child.height;
|
|
||||||
}
|
}
|
||||||
contentWidth = MAX(contentWidth, childSize.width + childConfig.margin.left + childConfig.margin.right);
|
contentWidth = MAX(contentWidth, childSize.width + childConfig.margin.left + childConfig.margin.right);
|
||||||
contentHeight = MAX(contentHeight, childSize.height + childConfig.margin.top + childConfig.margin.bottom);
|
contentHeight = MAX(contentHeight, childSize.height + childConfig.margin.top + childConfig.margin.bottom);
|
||||||
@ -159,38 +205,23 @@ - (CGSize)sizeContent:(CGSize)size {
|
|||||||
return CGSizeMake(contentWidth, contentHeight);
|
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) {
|
for (UIView *child in self.subviews) {
|
||||||
if (child.isHidden) {
|
if (child.isHidden) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
DoricLayoutConfig *childConfig = child.layoutConfig;
|
DoricLayoutConfig *childConfig = child.layoutConfig;
|
||||||
if (!childConfig) {
|
if (!childConfig) {
|
||||||
childConfig = [DoricLayoutConfig new];
|
childConfig = [DoricLayoutConfig new];
|
||||||
}
|
}
|
||||||
|
CGSize size = [child measureSize:CGSizeMake(targetSize.width, targetSize.height)];
|
||||||
CGSize size = [child sizeThatFits:CGSizeMake(targetSize.width, targetSize.height)];
|
[child layoutSelf:size];
|
||||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
DoricGravity gravity = childConfig.alignment;
|
||||||
size.width = child.width;
|
|
||||||
}
|
|
||||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
|
||||||
size.height = child.height;
|
|
||||||
}
|
|
||||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
|
||||||
size.width = child.width;
|
|
||||||
} else if (childConfig.widthSpec == DoricLayoutAtMost) {
|
|
||||||
size.width = targetSize.width;
|
|
||||||
}
|
|
||||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
|
||||||
size.height = child.height;
|
|
||||||
} else if (childConfig.heightSpec == DoricLayoutAtMost) {
|
|
||||||
size.height = targetSize.height;
|
|
||||||
}
|
|
||||||
child.width = size.width;
|
|
||||||
child.height = size.height;
|
|
||||||
|
|
||||||
DoricGravity gravity = childConfig.alignment | self.gravity;
|
|
||||||
|
|
||||||
if ((gravity & LEFT) == LEFT) {
|
if ((gravity & LEFT) == LEFT) {
|
||||||
child.left = 0;
|
child.left = 0;
|
||||||
} else if ((gravity & RIGHT) == RIGHT) {
|
} else if ((gravity & RIGHT) == RIGHT) {
|
||||||
@ -204,7 +235,6 @@ - (void)layout:(CGSize)targetSize {
|
|||||||
child.right = targetSize.width - childConfig.margin.right;
|
child.right = targetSize.width - childConfig.margin.right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((gravity & TOP) == TOP) {
|
if ((gravity & TOP) == TOP) {
|
||||||
child.top = 0;
|
child.top = 0;
|
||||||
} else if ((gravity & BOTTOM) == BOTTOM) {
|
} else if ((gravity & BOTTOM) == BOTTOM) {
|
||||||
@ -218,13 +248,7 @@ - (void)layout:(CGSize)targetSize {
|
|||||||
child.bottom = targetSize.height - childConfig.margin.bottom;
|
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
|
@end
|
||||||
|
|
||||||
@ -232,7 +256,8 @@ @implementation DoricLinearView
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation DoricVLayoutView
|
@implementation DoricVLayoutView
|
||||||
- (CGSize)sizeContent:(CGSize)size {
|
|
||||||
|
- (CGSize)sizeThatFits:(CGSize)size {
|
||||||
CGFloat contentWidth = 0;
|
CGFloat contentWidth = 0;
|
||||||
CGFloat contentHeight = 0;
|
CGFloat contentHeight = 0;
|
||||||
NSUInteger contentWeight = 0;
|
NSUInteger contentWeight = 0;
|
||||||
@ -244,24 +269,11 @@ - (CGSize)sizeContent:(CGSize)size {
|
|||||||
if (!childConfig) {
|
if (!childConfig) {
|
||||||
childConfig = [DoricLayoutConfig new];
|
childConfig = [DoricLayoutConfig new];
|
||||||
}
|
}
|
||||||
CGSize childSize = CGSizeMake(child.width, child.height);
|
CGSize childSize;
|
||||||
if ([child isKindOfClass:[DoricLayoutContainer class]]
|
if (CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
|
||||||
|| childConfig.widthSpec == DoricLayoutWrapContent
|
childSize = [child measureSize:CGSizeMake(size.width, size.height - contentHeight)];
|
||||||
|| childConfig.heightSpec == DoricLayoutWrapContent) {
|
} else {
|
||||||
childSize = [child sizeThatFits:CGSizeMake(size.width, size.height - contentHeight)];
|
childSize = child.bounds.size;
|
||||||
}
|
|
||||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
|
||||||
childSize.width = child.width;
|
|
||||||
} else if (childConfig.widthSpec == DoricLayoutAtMost) {
|
|
||||||
childSize.width = size.width;
|
|
||||||
}
|
|
||||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
|
||||||
childSize.height = child.height;
|
|
||||||
} else if (childConfig.heightSpec == DoricLayoutAtMost) {
|
|
||||||
childSize.height = size.height - contentHeight;
|
|
||||||
}
|
|
||||||
if (childConfig.weight) {
|
|
||||||
childSize.height = child.height;
|
|
||||||
}
|
}
|
||||||
contentWidth = MAX(contentWidth, childSize.width + childConfig.margin.left + childConfig.margin.right);
|
contentWidth = MAX(contentWidth, childSize.width + childConfig.margin.left + childConfig.margin.right);
|
||||||
contentHeight += childSize.height + self.space + childConfig.margin.top + childConfig.margin.bottom;
|
contentHeight += childSize.height + self.space + childConfig.margin.top + childConfig.margin.bottom;
|
||||||
@ -277,7 +289,9 @@ - (CGSize)sizeContent:(CGSize)size {
|
|||||||
return CGSizeMake(contentWidth, contentHeight);
|
return CGSizeMake(contentWidth, contentHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)layout:(CGSize)targetSize {
|
- (void)layoutSelf:(CGSize)targetSize {
|
||||||
|
self.width = targetSize.width;
|
||||||
|
self.height = targetSize.height;
|
||||||
CGFloat yStart = 0;
|
CGFloat yStart = 0;
|
||||||
if ((self.gravity & TOP) == TOP) {
|
if ((self.gravity & TOP) == TOP) {
|
||||||
yStart = 0;
|
yStart = 0;
|
||||||
@ -291,40 +305,20 @@ - (void)layout:(CGSize)targetSize {
|
|||||||
if (child.isHidden) {
|
if (child.isHidden) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
DoricLayoutConfig *childConfig = child.layoutConfig;
|
DoricLayoutConfig *childConfig = child.layoutConfig;
|
||||||
if (!childConfig) {
|
if (!childConfig) {
|
||||||
childConfig = [DoricLayoutConfig new];
|
childConfig = [DoricLayoutConfig new];
|
||||||
}
|
}
|
||||||
|
|
||||||
CGSize size = [child sizeThatFits:CGSizeMake(targetSize.width, targetSize.height - yStart)];
|
CGSize size = [child measureSize:CGSizeMake(targetSize.width, targetSize.height - yStart)];
|
||||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
|
||||||
size.width = child.width;
|
|
||||||
}
|
|
||||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
|
||||||
size.height = child.height;
|
|
||||||
}
|
|
||||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
|
||||||
size.width = child.width;
|
|
||||||
} else if (childConfig.widthSpec == DoricLayoutAtMost) {
|
|
||||||
size.width = targetSize.width;
|
|
||||||
}
|
|
||||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
|
||||||
size.height = child.height;
|
|
||||||
} else if (childConfig.heightSpec == DoricLayoutAtMost) {
|
|
||||||
size.height = targetSize.height - yStart;
|
|
||||||
}
|
|
||||||
if (childConfig.weight) {
|
|
||||||
size.height = child.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (childConfig.weight) {
|
if (childConfig.weight) {
|
||||||
size.height += remain / self.contentWeight * childConfig.weight;
|
size.height += remain / self.contentWeight * childConfig.weight;
|
||||||
}
|
}
|
||||||
child.width = size.width;
|
[child layoutSelf:size];
|
||||||
child.height = size.height;
|
|
||||||
|
|
||||||
DoricGravity gravity = childConfig.alignment | self.gravity;
|
DoricGravity gravity = childConfig.alignment | self.gravity;
|
||||||
|
|
||||||
if ((gravity & LEFT) == LEFT) {
|
if ((gravity & LEFT) == LEFT) {
|
||||||
child.left = 0;
|
child.left = 0;
|
||||||
} else if ((gravity & RIGHT) == RIGHT) {
|
} else if ((gravity & RIGHT) == RIGHT) {
|
||||||
@ -346,18 +340,12 @@ - (void)layout:(CGSize)targetSize {
|
|||||||
if (childConfig.margin.bottom) {
|
if (childConfig.margin.bottom) {
|
||||||
yStart += 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
|
@end
|
||||||
|
|
||||||
@implementation DoricHLayoutView
|
@implementation DoricHLayoutView
|
||||||
|
- (CGSize)sizeThatFits:(CGSize)size {
|
||||||
- (CGSize)sizeContent:(CGSize)size {
|
|
||||||
CGFloat contentWidth = 0;
|
CGFloat contentWidth = 0;
|
||||||
CGFloat contentHeight = 0;
|
CGFloat contentHeight = 0;
|
||||||
NSUInteger contentWeight = 0;
|
NSUInteger contentWeight = 0;
|
||||||
@ -369,24 +357,11 @@ - (CGSize)sizeContent:(CGSize)size {
|
|||||||
if (!childConfig) {
|
if (!childConfig) {
|
||||||
childConfig = [DoricLayoutConfig new];
|
childConfig = [DoricLayoutConfig new];
|
||||||
}
|
}
|
||||||
CGSize childSize = CGSizeMake(child.width, child.height);
|
CGSize childSize;
|
||||||
if ([child isKindOfClass:[DoricLayoutContainer class]]
|
if (CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
|
||||||
|| childConfig.widthSpec == DoricLayoutWrapContent
|
childSize = [child measureSize:CGSizeMake(size.width - contentWidth, size.height)];
|
||||||
|| childConfig.heightSpec == DoricLayoutWrapContent) {
|
} else {
|
||||||
childSize = [child sizeThatFits:CGSizeMake(size.width - contentWidth, size.height)];
|
childSize = child.bounds.size;
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
contentWidth += childSize.width + self.space + childConfig.margin.left + childConfig.margin.right;
|
contentWidth += childSize.width + self.space + childConfig.margin.left + childConfig.margin.right;
|
||||||
contentHeight = MAX(contentHeight, childSize.height + childConfig.margin.top + childConfig.margin.bottom);
|
contentHeight = MAX(contentHeight, childSize.height + childConfig.margin.top + childConfig.margin.bottom);
|
||||||
@ -402,7 +377,9 @@ - (CGSize)sizeContent:(CGSize)size {
|
|||||||
return CGSizeMake(contentWidth, contentHeight);
|
return CGSizeMake(contentWidth, contentHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)layout:(CGSize)targetSize {
|
- (void)layoutSelf:(CGSize)targetSize {
|
||||||
|
self.width = targetSize.width;
|
||||||
|
self.height = targetSize.height;
|
||||||
CGFloat xStart = 0;
|
CGFloat xStart = 0;
|
||||||
if (self.contentWeight) {
|
if (self.contentWeight) {
|
||||||
xStart = 0;
|
xStart = 0;
|
||||||
@ -418,37 +395,20 @@ - (void)layout:(CGSize)targetSize {
|
|||||||
if (child.isHidden) {
|
if (child.isHidden) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
DoricLayoutConfig *childConfig = child.layoutConfig;
|
DoricLayoutConfig *childConfig = child.layoutConfig;
|
||||||
if (!childConfig) {
|
if (!childConfig) {
|
||||||
childConfig = [DoricLayoutConfig new];
|
childConfig = [DoricLayoutConfig new];
|
||||||
}
|
}
|
||||||
|
|
||||||
CGSize size = [child sizeThatFits:CGSizeMake(targetSize.width - xStart, targetSize.height)];
|
CGSize size = [child measureSize:CGSizeMake(targetSize.width - xStart, targetSize.height)];
|
||||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
|
||||||
size.width = child.width;
|
|
||||||
}
|
|
||||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
|
||||||
size.height = child.height;
|
|
||||||
}
|
|
||||||
if (childConfig.widthSpec == DoricLayoutExact) {
|
|
||||||
size.width = child.width;
|
|
||||||
} else if (childConfig.widthSpec == DoricLayoutAtMost) {
|
|
||||||
size.width = targetSize.width - xStart;
|
|
||||||
}
|
|
||||||
if (childConfig.heightSpec == DoricLayoutExact) {
|
|
||||||
size.height = child.height;
|
|
||||||
} else if (childConfig.heightSpec == DoricLayoutAtMost) {
|
|
||||||
size.height = targetSize.height;
|
|
||||||
}
|
|
||||||
if (childConfig.weight) {
|
|
||||||
size.width = child.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (childConfig.weight) {
|
if (childConfig.weight) {
|
||||||
size.width += remain / self.contentWeight * 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;
|
DoricGravity gravity = childConfig.alignment | self.gravity;
|
||||||
if ((gravity & TOP) == TOP) {
|
if ((gravity & TOP) == TOP) {
|
||||||
@ -473,45 +433,10 @@ - (void)layout:(CGSize)targetSize {
|
|||||||
if (childConfig.margin.right) {
|
if (childConfig.margin.right) {
|
||||||
xStart += 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
|
@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 *vLayout(NSArray <__kindof UIView *> *views) {
|
||||||
DoricVLayoutView *layout = [[DoricVLayoutView alloc] initWithFrame:CGRectZero];
|
DoricVLayoutView *layout = [[DoricVLayoutView alloc] initWithFrame:CGRectZero];
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#import "DoricListNode.h"
|
#import "DoricListNode.h"
|
||||||
#import "DoricExtensions.h"
|
#import "DoricExtensions.h"
|
||||||
#import "DoricListItemNode.h"
|
#import "DoricListItemNode.h"
|
||||||
|
#import "DoricLayouts.h"
|
||||||
|
|
||||||
@interface DoricTableViewCell : UITableViewCell
|
@interface DoricTableViewCell : UITableViewCell
|
||||||
@property(nonatomic, strong) DoricListItemNode *doricListItemNode;
|
@property(nonatomic, strong) DoricListItemNode *doricListItemNode;
|
||||||
@ -36,13 +37,22 @@ @implementation DoricTableView
|
|||||||
- (CGSize)sizeThatFits:(CGSize)size {
|
- (CGSize)sizeThatFits:(CGSize)size {
|
||||||
if (self.subviews.count > 0) {
|
if (self.subviews.count > 0) {
|
||||||
CGFloat width = size.width;
|
CGFloat width = size.width;
|
||||||
|
CGFloat height = 0;
|
||||||
|
|
||||||
for (UIView *child in self.subviews) {
|
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;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)layoutSelf:(CGSize)targetSize {
|
||||||
|
[super layoutSelf:targetSize];
|
||||||
|
[self reloadData];
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
@ -74,9 +84,11 @@ - (UITableView *)build {
|
|||||||
- (void)blendView:(UITableView *)view forPropName:(NSString *)name propValue:(id)prop {
|
- (void)blendView:(UITableView *)view forPropName:(NSString *)name propValue:(id)prop {
|
||||||
if ([@"itemCount" isEqualToString:name]) {
|
if ([@"itemCount" isEqualToString:name]) {
|
||||||
self.itemCount = [prop unsignedIntegerValue];
|
self.itemCount = [prop unsignedIntegerValue];
|
||||||
|
[self.view reloadData];
|
||||||
} else if ([@"renderItem" isEqualToString:name]) {
|
} else if ([@"renderItem" isEqualToString:name]) {
|
||||||
[self.itemViewIds removeAllObjects];
|
[self.itemViewIds removeAllObjects];
|
||||||
[self clearSubModel];
|
[self clearSubModel];
|
||||||
|
[self.view reloadData];
|
||||||
} else if ([@"batchCount" isEqualToString:name]) {
|
} else if ([@"batchCount" isEqualToString:name]) {
|
||||||
self.batchCount = [prop unsignedIntegerValue];
|
self.batchCount = [prop unsignedIntegerValue];
|
||||||
} else {
|
} else {
|
||||||
@ -109,8 +121,8 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N
|
|||||||
DoricListItemNode *node = cell.doricListItemNode;
|
DoricListItemNode *node = cell.doricListItemNode;
|
||||||
node.viewId = model[@"id"];
|
node.viewId = model[@"id"];
|
||||||
[node blend:props];
|
[node blend:props];
|
||||||
[node.view setNeedsLayout];
|
CGSize size = [node.view measureSize:CGSizeMake(tableView.width, tableView.height)];
|
||||||
CGSize size = [node.view sizeThatFits:CGSizeMake(cell.width, cell.height)];
|
[node.view layoutSelf:size];
|
||||||
[self callItem:position height:size.height];
|
[self callItem:position height:size.height];
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
@ -22,5 +22,9 @@
|
|||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import "DoricSuperNode.h"
|
#import "DoricSuperNode.h"
|
||||||
|
|
||||||
@interface DoricScrollerNode : DoricSuperNode<UIScrollView *>
|
@interface DoricScrollView : UIScrollView
|
||||||
|
@property(nonatomic, strong) UIView *contentView;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface DoricScrollerNode : DoricSuperNode<DoricScrollView *>
|
||||||
@end
|
@end
|
@ -22,27 +22,28 @@
|
|||||||
#import "DoricScrollerNode.h"
|
#import "DoricScrollerNode.h"
|
||||||
#import "DoricExtensions.h"
|
#import "DoricExtensions.h"
|
||||||
|
|
||||||
@interface DoricScrollView : UIScrollView
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation DoricScrollView
|
@implementation DoricScrollView
|
||||||
|
|
||||||
- (void)layoutSubviews {
|
- (void)setContentView:(UIView *)contentView {
|
||||||
[super layoutSubviews];
|
if (_contentView) {
|
||||||
if (self.subviews.count > 0) {
|
[_contentView removeFromSuperview];
|
||||||
UIView *child = self.subviews[0];
|
|
||||||
[self setContentSize:child.frame.size];
|
|
||||||
}
|
}
|
||||||
|
_contentView = contentView;
|
||||||
|
[self addSubview:contentView];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CGSize)sizeThatFits:(CGSize)size {
|
- (CGSize)sizeThatFits:(CGSize)size {
|
||||||
if (self.subviews.count > 0) {
|
if (self.contentView) {
|
||||||
UIView *child = self.subviews[0];
|
return [self.contentView sizeThatFits:size];
|
||||||
CGSize childSize = [child sizeThatFits:size];
|
|
||||||
return CGSizeMake(MIN(size.width, childSize.width), MIN(size.height, childSize.height));
|
|
||||||
}
|
}
|
||||||
return CGSizeZero;
|
return CGSizeZero;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)layoutSelf:(CGSize)targetSize {
|
||||||
|
[super layoutSelf:targetSize];
|
||||||
|
[self setContentSize:self.contentView.frame.size];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface DoricScrollerNode ()
|
@interface DoricScrollerNode ()
|
||||||
@ -51,7 +52,7 @@ @interface DoricScrollerNode ()
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation DoricScrollerNode
|
@implementation DoricScrollerNode
|
||||||
- (UIScrollView *)build {
|
- (DoricScrollView *)build {
|
||||||
return [DoricScrollView new];
|
return [DoricScrollView new];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,12 +75,11 @@ - (void)blend:(NSDictionary *)props {
|
|||||||
[it blend:childProps];
|
[it blend:childProps];
|
||||||
}];
|
}];
|
||||||
} else {
|
} else {
|
||||||
[self.childNode.view removeFromSuperview];
|
|
||||||
self.childNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
|
self.childNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
|
||||||
it.viewId = viewId;
|
it.viewId = viewId;
|
||||||
[it initWithSuperNode:self];
|
[it initWithSuperNode:self];
|
||||||
[it blend:childProps];
|
[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.viewId = viewId;
|
||||||
[it initWithSuperNode:self];
|
[it initWithSuperNode:self];
|
||||||
[it blend:childProps];
|
[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]) {
|
if ([@"content" isEqualToString:name]) {
|
||||||
self.childViewId = prop;
|
self.childViewId = prop;
|
||||||
} else {
|
} else {
|
||||||
@ -104,4 +104,11 @@ - (void)blendView:(UIScrollView *)view forPropName:(NSString *)name propValue:(i
|
|||||||
- (void)blendSubNode:(NSDictionary *)subModel {
|
- (void)blendSubNode:(NSDictionary *)subModel {
|
||||||
[self.childNode blend:subModel[@"props"]];
|
[self.childNode blend:subModel[@"props"]];
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId {
|
||||||
|
if ([viewId isEqualToString:self.childViewId]) {
|
||||||
|
return self.childNode;
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
@ -25,6 +25,9 @@ @interface DoricSlideItemView : DoricStackView
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation DoricSlideItemView
|
@implementation DoricSlideItemView
|
||||||
|
- (void)layoutSubviews {
|
||||||
|
[super layoutSubviews];
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation DoricSlideItemNode
|
@implementation DoricSlideItemNode
|
||||||
|
@ -45,12 +45,18 @@ - (CGSize)sizeThatFits:(CGSize)size {
|
|||||||
if (self.subviews.count > 0) {
|
if (self.subviews.count > 0) {
|
||||||
CGFloat height = size.height;
|
CGFloat height = size.height;
|
||||||
for (UIView *child in self.subviews) {
|
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;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)layoutSelf:(CGSize)targetSize {
|
||||||
|
[super layoutSelf:targetSize];
|
||||||
|
[self reloadData];
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation DoricSliderNode
|
@implementation DoricSliderNode
|
||||||
@ -80,9 +86,11 @@ - (UICollectionView *)build {
|
|||||||
- (void)blendView:(UICollectionView *)view forPropName:(NSString *)name propValue:(id)prop {
|
- (void)blendView:(UICollectionView *)view forPropName:(NSString *)name propValue:(id)prop {
|
||||||
if ([@"itemCount" isEqualToString:name]) {
|
if ([@"itemCount" isEqualToString:name]) {
|
||||||
self.itemCount = [prop unsignedIntegerValue];
|
self.itemCount = [prop unsignedIntegerValue];
|
||||||
|
[self.view reloadData];
|
||||||
} else if ([@"renderPage" isEqualToString:name]) {
|
} else if ([@"renderPage" isEqualToString:name]) {
|
||||||
[self.itemViewIds removeAllObjects];
|
[self.itemViewIds removeAllObjects];
|
||||||
[self clearSubModel];
|
[self clearSubModel];
|
||||||
|
[self.view reloadData];
|
||||||
} else if ([@"batchCount" isEqualToString:name]) {
|
} else if ([@"batchCount" isEqualToString:name]) {
|
||||||
self.batchCount = [prop unsignedIntegerValue];
|
self.batchCount = [prop unsignedIntegerValue];
|
||||||
} else {
|
} else {
|
||||||
@ -120,7 +128,8 @@ - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collection
|
|||||||
DoricSlideItemNode *node = cell.doricSlideItemNode;
|
DoricSlideItemNode *node = cell.doricSlideItemNode;
|
||||||
node.viewId = model[@"id"];
|
node.viewId = model[@"id"];
|
||||||
[node blend:props];
|
[node blend:props];
|
||||||
[node.view setNeedsLayout];
|
CGSize size = [node.view measureSize:CGSizeMake(collectionView.width, collectionView.height)];
|
||||||
|
[node.view layoutSelf:size];
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,9 +150,11 @@ - (void)clearSubModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId {
|
- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId {
|
||||||
|
NSAssert(NO, @"Should override class:%@ ,method:%@.", NSStringFromClass([self class]),
|
||||||
|
NSStringFromSelector(_cmd));
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
- (void)requestLayout {
|
- (void)requestLayout {
|
||||||
[self.view setNeedsLayout];
|
[self.view setNeedsLayout];
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
@ -232,4 +232,18 @@ - (NSNumber *)getHeight {
|
|||||||
return @(self.view.height);
|
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
|
@end
|
||||||
|
@ -32,6 +32,7 @@ extern NSString *const INJECT_REQUIRE;
|
|||||||
extern NSString *const INJECT_TIMER_SET;
|
extern NSString *const INJECT_TIMER_SET;
|
||||||
extern NSString *const INJECT_TIMER_CLEAR;
|
extern NSString *const INJECT_TIMER_CLEAR;
|
||||||
extern NSString *const INJECT_BRIDGE;
|
extern NSString *const INJECT_BRIDGE;
|
||||||
|
extern NSString *const INJECT_EMPTY;
|
||||||
|
|
||||||
extern NSString *const TEMPLATE_CONTEXT_CREATE;
|
extern NSString *const TEMPLATE_CONTEXT_CREATE;
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
NSString *const INJECT_TIMER_SET = @"nativeSetTimer";
|
NSString *const INJECT_TIMER_SET = @"nativeSetTimer";
|
||||||
NSString *const INJECT_TIMER_CLEAR = @"nativeClearTimer";
|
NSString *const INJECT_TIMER_CLEAR = @"nativeClearTimer";
|
||||||
NSString *const INJECT_BRIDGE = @"nativeBridge";
|
NSString *const INJECT_BRIDGE = @"nativeBridge";
|
||||||
|
NSString *const INJECT_EMPTY = @"nativeEmpty";
|
||||||
|
|
||||||
NSString *const TEMPLATE_CONTEXT_CREATE = @"Reflect.apply("
|
NSString *const TEMPLATE_CONTEXT_CREATE = @"Reflect.apply("
|
||||||
"function(doric,context,Entry,require,exports){" "\n"
|
"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/widgets"
|
||||||
export * from "./src/ui/panel"
|
export * from "./src/ui/panel"
|
||||||
export * from "./src/ui/declarative"
|
export * from "./src/ui/declarative"
|
||||||
|
export * from "./src/ui/refreshable"
|
||||||
export * from "./src/util/color"
|
export * from "./src/util/color"
|
||||||
export * from './src/util/log'
|
export * from './src/util/log'
|
||||||
export * from './src/util/types'
|
export * from './src/util/types'
|
||||||
|
@ -57,6 +57,10 @@ export class List extends Superview implements IList {
|
|||||||
@Property
|
@Property
|
||||||
batchCount = 15
|
batchCount = 15
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.cachedViews.clear()
|
||||||
|
this.itemCount = 0
|
||||||
|
}
|
||||||
private getItem(itemIdx: number) {
|
private getItem(itemIdx: number) {
|
||||||
let view = this.cachedViews.get(`${itemIdx}`)
|
let view = this.cachedViews.get(`${itemIdx}`)
|
||||||
if (view === undefined) {
|
if (view === undefined) {
|
||||||
|
@ -31,6 +31,8 @@ export function NativeCall(target: Panel, propertyKey: string, descriptor: Prope
|
|||||||
|
|
||||||
type Frame = { width: number, height: number }
|
type Frame = { width: number, height: number }
|
||||||
|
|
||||||
|
declare function nativeEmpty(): void
|
||||||
|
|
||||||
export abstract class Panel {
|
export abstract class Panel {
|
||||||
context?: any
|
context?: any
|
||||||
onCreate() { }
|
onCreate() { }
|
||||||
@ -132,7 +134,7 @@ export abstract class Panel {
|
|||||||
|
|
||||||
private hookAfterNativeCall() {
|
private hookAfterNativeCall() {
|
||||||
//Here insert a native call to ensure the promise is resolved done.
|
//Here insert a native call to ensure the promise is resolved done.
|
||||||
log('Check Dirty')
|
nativeEmpty()
|
||||||
if (this.__root__.isDirty()) {
|
if (this.__root__.isDirty()) {
|
||||||
const model = this.__root__.toModel()
|
const model = this.__root__.toModel()
|
||||||
this.nativeRender(model)
|
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 { uniqueId } from "../util/uniqueId";
|
||||||
import { Gravity } from "../util/gravity";
|
import { Gravity } from "../util/gravity";
|
||||||
import { loge } from "../util/log";
|
import { loge } from "../util/log";
|
||||||
|
import { BridgeContext } from "../runtime/global";
|
||||||
|
|
||||||
export enum LayoutSpec {
|
export enum LayoutSpec {
|
||||||
EXACTLY = 0,
|
EXACTLY = 0,
|
||||||
@ -266,7 +267,7 @@ export abstract class View implements Modeling, IView {
|
|||||||
|
|
||||||
nativeChannel(context: any, name: string) {
|
nativeChannel(context: any, name: string) {
|
||||||
let thisView: View | undefined = this
|
let thisView: View | undefined = this
|
||||||
return function (...args: any) {
|
return function (args: any = undefined) {
|
||||||
const func = context.shader.command
|
const func = context.shader.command
|
||||||
const viewIds = []
|
const viewIds = []
|
||||||
while (thisView != undefined) {
|
while (thisView != undefined) {
|
||||||
@ -281,6 +282,29 @@ export abstract class View implements Modeling, IView {
|
|||||||
return Reflect.apply(func, undefined, [params]) as Promise<any>
|
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 {
|
export abstract class Superview extends View {
|
||||||
@ -316,6 +340,7 @@ export abstract class Superview extends View {
|
|||||||
toModel() {
|
toModel() {
|
||||||
const subviews = []
|
const subviews = []
|
||||||
for (let v of this.allSubviews()) {
|
for (let v of this.allSubviews()) {
|
||||||
|
v.superview = this
|
||||||
if (v.isDirty()) {
|
if (v.isDirty()) {
|
||||||
subviews.push(v.toModel())
|
subviews.push(v.toModel())
|
||||||
}
|
}
|
||||||
@ -341,7 +366,6 @@ export abstract class Group extends Superview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addChild(view: View) {
|
addChild(view: View) {
|
||||||
view.superview = this
|
|
||||||
this.children.push(view)
|
this.children.push(view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
// "incremental": true, /* Enable incremental compilation */
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
"target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
"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'. */
|
"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. */
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
// "checkJs": true, /* Report errors in .js files. */
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||||
|
Reference in New Issue
Block a user