在本地做装修在那个网站好线上营销活动方案
关键计算地方:
1.当前是上滑动还是下滑动(相对于屏幕) ,使用ev.getRawY()获得当前滑动位置在屏幕哪个地方
2. 计算文本客滑动到哪里即可停止, (行高*总文本行数)- (行高 * 最多显示行数) int sum = getLineHeight() * getLineCount() - getLineHeight() * getMaxLines();
代码:
import android.content.Context;
import android.text.method.ScrollingMovementMethod;
import android.util.AttributeSet;
import android.view.MotionEvent;/*** @Description: 可滑动的TextView, 并且解决了与 ScrollView等的滑动冲突*/
public class ScrollTextView extends android.support.v7.widget.AppCompatTextView {public ScrollTextView(Context context) {super(context);setMovementMethod(ScrollingMovementMethod.getInstance());}public ScrollTextView(Context context, AttributeSet attrs) {super(context, attrs);setMovementMethod(ScrollingMovementMethod.getInstance());}public ScrollTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);setMovementMethod(ScrollingMovementMethod.getInstance());}float lastScrollY = 0;@Overridepublic boolean onTouchEvent(MotionEvent ev) {if (getLineCount() > getMaxLines()) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {lastScrollY = ev.getRawY();} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {//滑动到头并且还在继续上滑动,或者滑动到底部就不要再拦截了(有误差)int sum = getLineHeight() * getLineCount() - getLineHeight() * getMaxLines();//计算上次与本次差float diff = lastScrollY - ev.getRawY();if (diff>0){//下滑动并且到达了底部也不要处理了//底部这里用abs的原因是,因为计算sum的时候有些误差if (Math.abs(sum - getScrollY())<5) {getParent().requestDisallowInterceptTouchEvent(false);} else {getParent().requestDisallowInterceptTouchEvent(true);}}else if (diff<0){//上滑动if (getScrollY() == 0) {//上滑动并且已经到达了顶部就不要在处理了getParent().requestDisallowInterceptTouchEvent(false);} else {getParent().requestDisallowInterceptTouchEvent(true);}}lastScrollY = ev.getRawY();} else {getParent().requestDisallowInterceptTouchEvent(false);}}return super.onTouchEvent(ev);}
}
如果上面方法不能解决你的问题,那就参考下面的文章,让textview实现 NestedScroolChild3 接口,并重写相应的方法,从而实现TextView嵌套滑动。
WebView 实现嵌套滑动,丝滑般实现吸顶效果,完美兼容 X5 webview
我使用的代码:
package com.**.view;
import android.annotation.SuppressLint;
import android.text.StaticLayout;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.Layout;
import android.text.TextPaint;
import android.text.method.ScrollingMovementMethod;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewParent;
import android.widget.OverScroller;
import android.widget.TextView;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.NestedScrollingChild3;
import androidx.core.view.NestedScrollingChildHelper;
import androidx.core.view.ViewCompat;public class JustifyTextView extends androidx.appcompat.widget.AppCompatTextView implements NestedScrollingChild3 {private static final String TAG = "JustifyTextView";private int mLineY;private int mViewWidth;private static final int INVALID_POINTER = -1;private final int[] mScrollOffset = new int[2];private final int[] mScrollConsumed = new int[2];private int mLastMotionY;private NestedScrollingChildHelper mChildHelper;private boolean mIsBeingDragged = false;private VelocityTracker mVelocityTracker;private int mTouchSlop;private int mActivePointerId = INVALID_POINTER;private int mNestedYOffset;private OverScroller mScroller;private int mMinimumVelocity;private int mMaximumVelocity;private int mLastScrollerY;public JustifyTextView(Context context) {super(context);init();}public JustifyTextView(Context context, AttributeSet attrs) {super(context, attrs);init();}public JustifyTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {mScroller = new OverScroller(getContext());setMovementMethod(ScrollingMovementMethod.getInstance());setOverScrollMode(TextView.OVER_SCROLL_NEVER);mChildHelper = new NestedScrollingChildHelper(this);setNestedScrollingEnabled(true);}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);}@Overrideprotected void onDraw(Canvas canvas) {TextPaint paint = getPaint();paint.setColor(getCurrentTextColor());// 返回绘制状态的资源ID数组表示视图的当前状态paint.drawableState = getDrawableState();// 对View上的内容进行测量后得到的View内容占据的宽度// 前提是你必须在父布局的onLayout()方法或者此View的onDraw()方法里调用measure(0,0);// 否则你得到的结果和getWidth()得到的结果一样。mViewWidth = getMeasuredWidth();// 获取文本String text = getText().toString();mLineY = 0;mLineY += getTextSize();// 获取用于显示当前文本的布局Layout layout = getLayout();if (layout == null) {return;}Paint.FontMetrics fm = paint.getFontMetrics();int textHeight = (int) (Math.ceil(fm.descent - fm.ascent));textHeight = (int) (textHeight * layout.getSpacingMultiplier() + layout.getSpacingAdd());for (int i = 0; i < layout.getLineCount(); i++) {// 返回文本中的指定行开头的偏移int lineStart = layout.getLineStart(i);// 返回文本中的指定行最后一个字符的偏移int lineEnd = layout.getLineEnd(i);float width = StaticLayout.getDesiredWidth(text, lineStart, lineEnd, getPaint());String line = text.substring(lineStart, lineEnd);if (line.equals("")) {break;}if (i < layout.getLineCount() - 1) {if (needScale(line)) {drawScaledText(canvas, lineStart, line, width);} else {
// canvas.drawText(line.replace("%", ""), 0, mLineY, paint);canvas.drawText(line, 0, mLineY, paint);}} else {canvas.drawText(line, 0, mLineY, paint);}// 增加行高mLineY += textHeight;}}private void drawScaledText(Canvas canvas, int lineStart, String line,float lineWidth) {float x = 0;if (isFirstLineOfParagraph(lineStart, line)) {String blanks = " ";canvas.drawText(blanks, x, mLineY, getPaint());float bw = StaticLayout.getDesiredWidth(blanks, getPaint());x += bw;line = line.substring(3);}int gapCount = line.length() - 1;int i = 0;if (line.length() > 2 && line.charAt(0) == 12288&& line.charAt(1) == 12288) {String substring = line.substring(0, 2);float cw = StaticLayout.getDesiredWidth(substring, getPaint());canvas.drawText(substring, x, mLineY, getPaint());x += cw;i += 2;}float d = (mViewWidth - lineWidth) / gapCount;for (; i < line.length(); i++) {String c = String.valueOf(line.charAt(i));float cw = StaticLayout.getDesiredWidth(c, getPaint());canvas.drawText(c, x, mLineY, getPaint());x += cw + d;}}private boolean isFirstLineOfParagraph(int lineStart, String line) {return line.length() > 3 && line.charAt(0) == ' ' && line.charAt(1) == ' ';}private boolean needScale(String line) {if (line.length() == 0) {return false;} else {
// return line.charAt(line.length() - 1) != '%';return line.charAt(line.length() - 1) != '\n';}}@SuppressLint("ClickableViewAccessibility")@Overridepublic boolean onTouchEvent(MotionEvent ev) {initVelocityTrackerIfNotExists();MotionEvent vtev = MotionEvent.obtain(ev);final int actionMasked = ev.getActionMasked();if (actionMasked == MotionEvent.ACTION_DOWN) {mNestedYOffset = 0;}vtev.offsetLocation(0, mNestedYOffset);switch (actionMasked) {case MotionEvent.ACTION_DOWN:if ((mIsBeingDragged = !mScroller.isFinished())) {final ViewParent parent = getParent();if (parent != null) {parent.requestDisallowInterceptTouchEvent(true);}}if (!mScroller.isFinished()) {abortAnimatedScroll();}mLastMotionY = (int) ev.getY();mActivePointerId = ev.getPointerId(0);startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);break;case MotionEvent.ACTION_MOVE:final int activePointerIndex = ev.findPointerIndex(mActivePointerId);if (activePointerIndex == -1) {Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");break;}final int y = (int) ev.getY(activePointerIndex);int deltaY = mLastMotionY - y;if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset,ViewCompat.TYPE_TOUCH)) {deltaY -= mScrollConsumed[1];mNestedYOffset += mScrollOffset[1];}if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {final ViewParent parent = getParent();if (parent != null) {parent.requestDisallowInterceptTouchEvent(true);}mIsBeingDragged = true;if (deltaY > 0) {deltaY -= mTouchSlop;} else {deltaY += mTouchSlop;}}if (mIsBeingDragged) {mLastMotionY = y - mScrollOffset[1];final int oldY = getScrollY();final int range = getScrollRange();// Calling overScrollByCompat will call onOverScrolled, which// calls onScrollChanged if applicable.if (overScrollByCompat(0, deltaY, 0, oldY, 0, range, 0,0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) {mVelocityTracker.clear();}final int scrolledDeltaY = getScrollY() - oldY;final int unconsumedY = deltaY - scrolledDeltaY;mScrollConsumed[1] = 0;dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset,ViewCompat.TYPE_TOUCH, mScrollConsumed);mLastMotionY -= mScrollOffset[1];mNestedYOffset += mScrollOffset[1];}break;case MotionEvent.ACTION_UP:final VelocityTracker velocityTracker = mVelocityTracker;velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);if ((Math.abs(initialVelocity) > mMinimumVelocity)) {if (!dispatchNestedPreFling(0, -initialVelocity)) {dispatchNestedFling(0, -initialVelocity, true);fling(-initialVelocity);}} else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,getScrollRange())) {ViewCompat.postInvalidateOnAnimation(this);}mActivePointerId = INVALID_POINTER;endDrag();break;case MotionEvent.ACTION_CANCEL:if (mIsBeingDragged) {if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,getScrollRange())) {ViewCompat.postInvalidateOnAnimation(this);}}mActivePointerId = INVALID_POINTER;endDrag();break;case MotionEvent.ACTION_POINTER_DOWN:final int index = ev.getActionIndex();mLastMotionY = (int) ev.getY(index);mActivePointerId = ev.getPointerId(index);break;case MotionEvent.ACTION_POINTER_UP:onSecondaryPointerUp(ev);mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));break;}if (mVelocityTracker != null) {mVelocityTracker.addMovement(vtev);}vtev.recycle();return super.onTouchEvent(ev);}private void abortAnimatedScroll() {mScroller.abortAnimation();stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);}private void endDrag() {mIsBeingDragged = false;recycleVelocityTracker();stopNestedScroll();}private void onSecondaryPointerUp(MotionEvent ev) {final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;final int pointerId = ev.getPointerId(pointerIndex);if (pointerId == mActivePointerId) {final int newPointerIndex = pointerIndex == 0 ? 1 : 0;mLastMotionY = (int) ev.getY(newPointerIndex);mActivePointerId = ev.getPointerId(newPointerIndex);if (mVelocityTracker != null) {mVelocityTracker.clear();}}}private void fling(int velocityY) {int height = getHeight();mScroller.fling(getScrollX(), getScrollY(), // start0, velocityY, // velocities0, 0, // xInteger.MIN_VALUE, Integer.MAX_VALUE, // y0, height / 2);runAnimatedScroll(true);}private void runAnimatedScroll(boolean participateInNestedScrolling) {if (participateInNestedScrolling) {startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);} else {stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);}mLastScrollerY = getScrollY();ViewCompat.postInvalidateOnAnimation(this);}private void initOrResetVelocityTracker() {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();} else {mVelocityTracker.clear();}}private void initVelocityTrackerIfNotExists() {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}}private void recycleVelocityTracker() {if (mVelocityTracker != null) {mVelocityTracker.recycle();mVelocityTracker = null;}}@Overrideprotected boolean overScrollBy(int deltaX, int deltaY,int scrollX, int scrollY,int scrollRangeX, int scrollRangeY,int maxOverScrollX, int maxOverScrollY,boolean isTouchEvent) {// this is causing double scroll call (doubled speed), but this WebView isn't overscrollable// all overscrolls are passed to appbar, so commenting this out during dragif (!mIsBeingDragged)overScrollByCompat(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,maxOverScrollX, maxOverScrollY, isTouchEvent);// without this call webview won't scroll to top when url change or when user pick input// (webview should move a bit making input still in viewport when "adjustResize")return true;}int getScrollRange() {//Using scroll range of webview instead of childs as NestedScrollView does.return computeVerticalScrollRange();}@Overridepublic boolean isNestedScrollingEnabled() {return mChildHelper.isNestedScrollingEnabled();}@Overridepublic void setNestedScrollingEnabled(boolean enabled) {mChildHelper.setNestedScrollingEnabled(enabled);}@Overridepublic boolean startNestedScroll(int axes, int type) {return mChildHelper.startNestedScroll(axes, type);}@Overridepublic boolean startNestedScroll(int axes) {return startNestedScroll(axes, ViewCompat.TYPE_TOUCH);}@Overridepublic void stopNestedScroll(int type) {mChildHelper.stopNestedScroll(type);}@Overridepublic void stopNestedScroll() {stopNestedScroll(ViewCompat.TYPE_TOUCH);}@Overridepublic boolean hasNestedScrollingParent(int type) {return mChildHelper.hasNestedScrollingParent(type);}@Overridepublic boolean hasNestedScrollingParent() {return hasNestedScrollingParent(ViewCompat.TYPE_TOUCH);}@Overridepublic boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,int[] offsetInWindow) {return dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,offsetInWindow, ViewCompat.TYPE_TOUCH);}@Overridepublic boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,int[] offsetInWindow, int type) {return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,offsetInWindow, type);}@Overridepublic void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,@Nullable int[] offsetInWindow, int type, @NonNull int[] consumed) {mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,offsetInWindow, type, consumed);}@Overridepublic boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, ViewCompat.TYPE_TOUCH);}@Overridepublic boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type) {return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);}@Overridepublic boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {return mChildHelper.dispatchNestedFling(velocityX, velocityY, false);}@Overridepublic boolean dispatchNestedPreFling(float velocityX, float velocityY) {return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);}@Overridepublic void computeScroll() {if (mScroller.isFinished()) {return;}mScroller.computeScrollOffset();final int y = mScroller.getCurrY();int unconsumed = y - mLastScrollerY;mLastScrollerY = y;// Nested Scrolling Pre PassmScrollConsumed[1] = 0;dispatchNestedPreScroll(0, unconsumed, mScrollConsumed, null,ViewCompat.TYPE_NON_TOUCH);unconsumed -= mScrollConsumed[1];if (unconsumed != 0) {// Internal Scrollfinal int oldScrollY = getScrollY();overScrollByCompat(0, unconsumed, getScrollX(), oldScrollY, 0, getScrollRange(),0, 0, false);final int scrolledByMe = getScrollY() - oldScrollY;unconsumed -= scrolledByMe;// Nested Scrolling Post PassmScrollConsumed[1] = 0;dispatchNestedScroll(0, 0, 0, unconsumed, mScrollOffset,ViewCompat.TYPE_NON_TOUCH, mScrollConsumed);unconsumed -= mScrollConsumed[1];}if (unconsumed != 0) {abortAnimatedScroll();}if (!mScroller.isFinished()) {ViewCompat.postInvalidateOnAnimation(this);}}// copied from NestedScrollView exacly as it looks, leaving overscroll related code, maybe future useprivate boolean overScrollByCompat(int deltaX, int deltaY,int scrollX, int scrollY,int scrollRangeX, int scrollRangeY,int maxOverScrollX, int maxOverScrollY,boolean isTouchEvent) {final int overScrollMode = getOverScrollMode();final boolean canScrollHorizontal =computeHorizontalScrollRange() > computeHorizontalScrollExtent();final boolean canScrollVertical =computeVerticalScrollRange() > computeVerticalScrollExtent();final boolean overScrollHorizontal = overScrollMode == View.OVER_SCROLL_ALWAYS|| (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);final boolean overScrollVertical = overScrollMode == View.OVER_SCROLL_ALWAYS|| (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);int newScrollX = scrollX + deltaX;if (!overScrollHorizontal) {maxOverScrollX = 0;}int newScrollY = scrollY + deltaY;if (!overScrollVertical) {maxOverScrollY = 0;}// Clamp values if at the limits and recordfinal int left = -maxOverScrollX;final int right = maxOverScrollX + scrollRangeX;final int top = -maxOverScrollY;final int bottom = maxOverScrollY + scrollRangeY;boolean clampedX = false;if (newScrollX > right) {newScrollX = right;clampedX = true;} else if (newScrollX < left) {newScrollX = left;clampedX = true;}boolean clampedY = false;if (newScrollY > bottom) {newScrollY = bottom;clampedY = true;} else if (newScrollY < top) {newScrollY = top;clampedY = true;}if (clampedY && !hasNestedScrollingParent(ViewCompat.TYPE_NON_TOUCH)) {mScroller.springBack(newScrollX, newScrollY, 0, 0, 0, getScrollRange());}onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);return clampedX || clampedY;}
}
分享:
定时垂直滚动的textview:GitHub - paradoxie/AutoVerticalTextview: 垂直滚动的textview,继承自TextSwitcher,抽出一个依赖库供以后备用
左右对齐的TextView
GitHub - Giftedcat/JustifyTextView: 左右对齐的TextView,适配各种分辨率,完美实现UI需求
JustifyTextView 自定义TextView解决中文排版-CSDN博客