blob: 5bb09779ab720712f7dbf08b58e403858381cd9d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.taobao.weex.ui.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Message;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ScrollView;
import com.taobao.weex.common.WXThread;
import com.taobao.weex.ui.component.WXComponent;
import com.taobao.weex.ui.component.WXScroller;
import com.taobao.weex.ui.view.gesture.WXGesture;
import com.taobao.weex.ui.view.gesture.WXGestureObservable;
import com.taobao.weex.utils.WXLogUtils;
import com.taobao.weex.utils.WXReflectionUtils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Custom-defined scrollView
*/
public class WXScrollView extends ScrollView implements Callback, IWXScroller,
WXGestureObservable,NestedScrollingChild {
private NestedScrollingChildHelper childHelper;
private float ox;
private float oy;
private int[] consumed = new int[2];
private int[] offsetInWindow = new int[2];
int mScrollX;
int mScrollY;
private WXGesture wxGesture;
private List<WXScrollViewListener> mScrollViewListeners;
private WXScroller mWAScroller;
//sticky
private View mCurrentStickyView;
private boolean mRedirectTouchToStickyView;
private int mStickyOffset;
private boolean mHasNotDoneActionDown = true;
@SuppressLint("HandlerLeak")
private Handler mScrollerTask;
private int mInitialPosition;
private int mCheckTime = 100;
/**
* The location of mCurrentStickyView
*/
private int[] mStickyP = new int[2];
/**
* Location of the scrollView
*/
private Rect mScrollRect;
private int[] stickyScrollerP = new int[2];
private int[] stickyViewP = new int[2];
private boolean scrollable = true;
public WXScrollView(Context context) {
super(context);
mScrollViewListeners = new ArrayList<>();
init();
try {
WXReflectionUtils.setValue(this, "mMinimumVelocity", 5);
} catch (Exception e) {
WXLogUtils.e("[WXScrollView] WXScrollView: ", e);
}
}
private void init() {
setWillNotDraw(false);
startScrollerTask();
setOverScrollMode(View.OVER_SCROLL_NEVER);
childHelper = new NestedScrollingChildHelper(this);
childHelper.setNestedScrollingEnabled(true);
}
public void startScrollerTask() {
if (mScrollerTask == null) {
mScrollerTask = new Handler(WXThread.secure(this));
}
mInitialPosition = getScrollY();
mScrollerTask.sendEmptyMessageDelayed(0, mCheckTime);
}
public WXScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public WXScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setOverScrollMode(View.OVER_SCROLL_NEVER);
}
/**
* Add listener for scrollView.
*/
public void addScrollViewListener(WXScrollViewListener scrollViewListener) {
if (!mScrollViewListeners.contains(scrollViewListener)) {
mScrollViewListeners.add(scrollViewListener);
}
}
public void removeScrollViewListener(WXScrollViewListener scrollViewListener) {
mScrollViewListeners.remove(scrollViewListener);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mRedirectTouchToStickyView = true;
}
if (mRedirectTouchToStickyView) {
mRedirectTouchToStickyView = mCurrentStickyView != null;
if (mRedirectTouchToStickyView) {
mRedirectTouchToStickyView = ev.getY() <= mCurrentStickyView.getHeight()
&& ev.getX() >= mCurrentStickyView.getLeft()
&& ev.getX() <= mCurrentStickyView.getRight();
}
}
if (mRedirectTouchToStickyView) {
if (mScrollRect == null) {
mScrollRect = new Rect();
getGlobalVisibleRect(mScrollRect);
}
mCurrentStickyView.getLocationOnScreen(stickyViewP);
ev.offsetLocation(0, stickyViewP[1] - mScrollRect.top);
}
boolean result = super.dispatchTouchEvent(ev);
if (wxGesture != null) {
result |= wxGesture.onTouch(this, ev);
}
return result;
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mCurrentStickyView != null) {
canvas.save();
mCurrentStickyView.getLocationOnScreen(mStickyP);
int realOffset = (mStickyOffset <= 0 ? mStickyOffset : 0);
canvas.translate(mStickyP[0], getScrollY() + realOffset);
canvas.clipRect(0, realOffset, mCurrentStickyView.getWidth(),
mCurrentStickyView.getHeight());
mCurrentStickyView.draw(canvas);
canvas.restore();
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if(!scrollable) {
return true; // when scrollable is set to false, then eat the touch event
}
if (mRedirectTouchToStickyView) {
if (mScrollRect == null) {
mScrollRect = new Rect();
getGlobalVisibleRect(mScrollRect);
}
mCurrentStickyView.getLocationOnScreen(stickyViewP);
ev.offsetLocation(0, -(stickyViewP[1] - mScrollRect.top));
}
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mHasNotDoneActionDown = false;
}
if (mHasNotDoneActionDown) {
MotionEvent down = MotionEvent.obtain(ev);
down.setAction(MotionEvent.ACTION_DOWN);
mHasNotDoneActionDown = false;
down.recycle();
}
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
ox = ev.getX();
oy = ev.getY();
// Dispatch touch event to parent view
startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL | ViewCompat.SCROLL_AXIS_VERTICAL);
}
if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
mHasNotDoneActionDown = true;
// stop nested scrolling dispatch
stopNestedScroll();
}
if (ev.getAction() == MotionEvent.ACTION_MOVE) {
float clampedX = ev.getX();
float clampedY = ev.getY();
int dx = (int) (ox - clampedX);
int dy = (int) (oy - clampedY);
if (dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)) {
// sub dx/dy was consumed by parent view!!!
ev.setLocation(clampedX+consumed[0],clampedY+consumed[1]);
}
ox = ev.getX();
oy = ev.getY();
}
return super.onTouchEvent(ev);
}
@Override
public void setNestedScrollingEnabled(boolean enabled) {
childHelper.setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return childHelper.isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return childHelper.startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
childHelper.stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return childHelper.hasNestedScrollingParent();
}
public boolean isScrollable() {
return scrollable;
}
public void setScrollable(boolean scrollable) {
this.scrollable = scrollable;
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
return childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return childHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return childHelper.dispatchNestedPreFling(velocityX, velocityY);
}
@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 void fling(int velocityY) {
super.fling(velocityY);
if (mScrollerTask != null) {
mScrollerTask.removeMessages(0);
}
startScrollerTask();
}
@Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
mScrollX = getScrollX();
mScrollY = getScrollY();
onScroll(WXScrollView.this, mScrollX, mScrollY);
View view = getChildAt(getChildCount() - 1);
if (view == null) {
return;
}
int d = view.getBottom();
d -= (getHeight() + mScrollY);
if (d == 0) {
onScrollToBottom(mScrollX, mScrollY);
}
int count = mScrollViewListeners == null ? 0 : mScrollViewListeners.size();
for (int i = 0; i < count; ++i) {
mScrollViewListeners.get(i).onScrollChanged(this, x, y, oldx, oldy);
}
showStickyView();
}
protected void onScroll(WXScrollView scrollView, int x, int y) {
int count = mScrollViewListeners == null ? 0 : mScrollViewListeners.size();
for (int i = 0; i < count; ++i) {
mScrollViewListeners.get(i).onScroll(this, x, y);
}
}
protected void onScrollToBottom(int x, int y) {
int count = mScrollViewListeners == null ? 0 : mScrollViewListeners.size();
for (int i = 0; i < count; ++i) {
mScrollViewListeners.get(i).onScrollToBottom(this, x, y);
}
}
private void showStickyView() {
if(mWAScroller == null){
return;
}
View curStickyView = procSticky(mWAScroller.getStickMap());
if (curStickyView != null) {
mCurrentStickyView = curStickyView;
} else {
mCurrentStickyView = null;
}
}
private View procSticky(Map<String, Map<String, WXComponent>> mStickyMap) {
if (mStickyMap == null) {
return null;
}
Map<String, WXComponent> stickyMap = mStickyMap.get(mWAScroller.getRef());
if (stickyMap == null) {
return null;
}
Iterator<Entry<String, WXComponent>> iterator = stickyMap.entrySet().iterator();
Entry<String, WXComponent> entry = null;
WXComponent stickyData;
while (iterator.hasNext()) {
entry = iterator.next();
stickyData = entry.getValue();
getLocationOnScreen(stickyScrollerP);
stickyData.getHostView().getLocationOnScreen(stickyViewP);
int parentH = 0;
if(stickyData.getParent()!=null && stickyData.getParent().getRealView()!=null){
parentH=stickyData.getParent().getRealView().getHeight();
}
int stickyViewH = stickyData.getHostView().getHeight();
int stickyShowPos = stickyScrollerP[1];
int stickyStartHidePos = -parentH + stickyScrollerP[1] + stickyViewH;
if (stickyViewP[1] <= stickyShowPos && stickyViewP[1] >= (stickyStartHidePos - stickyViewH)) {
mStickyOffset = stickyViewP[1] - stickyStartHidePos;
stickyData.setStickyOffset(stickyViewP[1]-stickyScrollerP[1]);
return stickyData.getHostView();
}else{
stickyData.setStickyOffset(0);
}
}
return null;
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 0:
if (mScrollerTask != null) {
mScrollerTask.removeMessages(0);
}
int newPosition = getScrollY();
if (mInitialPosition - newPosition == 0) {//has stopped
onScrollStopped(WXScrollView.this, getScrollX(), getScrollY());
} else {
onScroll(WXScrollView.this, getScrollX(), getScrollY());
mInitialPosition = getScrollY();
if (mScrollerTask != null) {
mScrollerTask.sendEmptyMessageDelayed(0, mCheckTime);
}
}
break;
default:
break;
}
return true;
}
protected void onScrollStopped(WXScrollView scrollView, int x, int y) {
int count = mScrollViewListeners == null ? 0 : mScrollViewListeners.size();
for (int i = 0; i < count; ++i) {
mScrollViewListeners.get(i).onScrollStopped(this, x, y);
}
}
@Override
public void destroy() {
if (mScrollerTask != null) {
mScrollerTask.removeCallbacksAndMessages(null);
}
}
@Override
public void registerGestureListener(WXGesture wxGesture) {
this.wxGesture = wxGesture;
}
public Rect getContentFrame() {
return new Rect(0, 0, computeHorizontalScrollRange(), computeVerticalScrollRange());
}
public interface WXScrollViewListener {
void onScrollChanged(WXScrollView scrollView, int x, int y, int oldx, int oldy);
void onScrollToBottom(WXScrollView scrollView, int x, int y);
void onScrollStopped(WXScrollView scrollView, int x, int y);
void onScroll(WXScrollView scrollView, int x, int y);
}
public void setWAScroller(WXScroller mWAScroller) {
this.mWAScroller = mWAScroller;
}
}