private Bitmap extentSnapshot(int doc, int gs, int spaceAround, boolean transparent) { final LogHelper log = new LogHelper(); final Rect extent = getDisplayExtent(); if (!extent.isEmpty()) { extent.inset(-spaceAround, -spaceAround); extent.intersect(0, 0, mView.getView().getWidth(), mView.getView().getHeight()); } if (extent.isEmpty()) { return null; } final Bitmap viewBitmap = mView.snapshot(doc, gs, transparent); if (viewBitmap == null) { return null; } if (extent.width() == mView.getView().getWidth() && extent.height() == mView.getView().getHeight()) { log.r(viewBitmap.getByteCount()); return viewBitmap; } final Bitmap realBitmap = Bitmap.createBitmap(viewBitmap, extent.left, extent.top, extent.width(), extent.height()); viewBitmap.recycle(); log.r(realBitmap.getByteCount()); return realBitmap; }
@Override protected void dispatchDraw(Canvas canvas) { View childView = getChildAt(mCurrentSelection); TextView childTextView = null; if (childView instanceof TextView) { childTextView = (TextView) childView; } if (childTextView != null && !TextUtils.isEmpty(childTextView.getText()) && getVisibility() == View.VISIBLE && childTextView.getVisibility() == View.VISIBLE) { Rect childRect = new Rect(); childTextView.getLocalVisibleRect(childRect); float childViewX = childTextView.getX() + childTextView.getWidth() / 2; float childViewY = childTextView.getY() + childTextView.getHeight() / 2; int top = (int) (childViewY - mSelectedRect.height() / 2); int left = (int) (childViewX - mSelectedRect.width() / 2); mSelectedRect.set(left, top, left + mSelectedRect.width(), top + mSelectedRect.height()); if (isShowSelected && !mSelectedRect.isEmpty()) { final Drawable selectedBg = mSelectedBg; selectedBg.setBounds(mSelectedRect); selectedBg.draw(canvas); } } super.dispatchDraw(canvas); }
/** * Computes whether the specified {@link android.graphics.Rect} intersects with the visible * portion of its parent {@link android.view.View}. Modifies {@code localRect} to contain only the * visible portion. * * @param localRect A rectangle in local (parent) coordinates. * @return Whether the specified {@link android.graphics.Rect} is visible on the screen. */ private boolean intersectVisibleToUser(Rect localRect) { // Missing or empty bounds mean this view is not visible. if ((localRect == null) || localRect.isEmpty()) { return false; } // Attached to invisible window means this view is not visible. if (mParentView.getWindowVisibility() != View.VISIBLE) { return false; } // An invisible predecessor or one with alpha zero means // that this view is not visible to the user. Object current = this; while (current instanceof View) { final View view = (View) current; // We have attach info so this view is attached and there is no // need to check whether we reach to ViewRootImpl on the way up. if ((view.getAlpha() <= 0) || (view.getVisibility() != View.VISIBLE)) { return false; } current = view.getParent(); } // If no portion of the parent is visible, this view is not visible. if (!mParentView.getLocalVisibleRect(mTempVisibleRect)) { return false; } // Check if the view intersects the visible portion of the parent. return localRect.intersect(mTempVisibleRect); }
public void commOnTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: y = ev.getY(); break; case MotionEvent.ACTION_UP: if (isNeedAnimation()) { animation(); } break; case MotionEvent.ACTION_MOVE: final float preY = y; float nowY = ev.getY(); int deltaY = (int) (preY - nowY) / size; y = nowY; if (isNeedMove()) { if (normal.isEmpty()) { normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom()); return; } int yy = inner.getTop() - deltaY; inner.layout(inner.getLeft(), yy, inner.getRight(), inner.getBottom() - deltaY); } break; default: break; } }
@Override public void draw(Canvas canvas) { if (null != mBitmap && !mBitmap.isRecycled()) { final Rect bounds = getBounds(); if (!bounds.isEmpty()) { canvas.drawBitmap(mBitmap, null, bounds, mPaint); } else { canvas.drawBitmap(mBitmap, 0f, 0f, mPaint); } } }
public static Rect preInvalidate(View view, Rect dirty) { if (dirty.isEmpty()) return dirty; Matrix m = getViewMatrix(view); if (m != null) { if (dirty != m_tempRect) { m_tempRect.set(dirty); dirty = m_tempRect; } transformFrame(m_tempRect, m); } return dirty; }
public void commOnTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: y = ev.getY(); break; case MotionEvent.ACTION_UP: if (isNeedAnimation()) { animation(); } // 加入额外代码 if (mAutoScrollViewPager != null) { mAutoScrollViewPager.startAutoScroll(); } y = DEFAULT_POSITION; break; /** * 排除第一次移动计算,因为第一次无法得知y左边,在MotionEvent.ACTION_DOWN中获取不到, * 因为此时是MyScrollView的Tocuh时间传递到了ListView的孩子item上面。所以从第二次开始计算 * 然而我们也要进行初始化,就是第一次移动的时候让滑动距离归零,之后记录准确了就正常执行 */ case MotionEvent.ACTION_MOVE: float preY = y; float nowY = ev.getY(); if (isDefaultPosition(y)) { preY = nowY; } int deltaY = (int) (preY - nowY); scrollBy(0, deltaY); y = nowY; // 当滚动到最上或者最下时就不会再滚动,这时移动布局 if (isNeedMove()) { if (normal.isEmpty()) { // 保存正常的布局位置 normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom()); } // 移动布局 inner.layout( inner.getLeft(), inner.getTop() - deltaY / 2, inner.getRight(), inner.getBottom() - deltaY / 2); } break; default: break; } }
private AccessibilityNodeInfoCompat populateNodeForItemInternal( T item, AccessibilityNodeInfoCompat node) { final int virtualDescendantId = getIdForItem(item); // Ensure the client has good defaults. node.setEnabled(true); // Allow the client to populate the node. populateNodeForItem(item, node); if (TextUtils.isEmpty(node.getText()) && TextUtils.isEmpty(node.getContentDescription())) { throw new RuntimeException( "You must add text or a content description in populateNodeForItem()"); } // Don't allow the client to override these properties. node.setPackageName(mParentView.getContext().getPackageName()); node.setClassName(item.getClass().getName()); node.setParent(mParentView); node.setSource(mParentView, virtualDescendantId); if (mFocusedItemId == virtualDescendantId) { node.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS); } else { node.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS); } node.getBoundsInParent(mTempParentRect); if (mTempParentRect.isEmpty()) { throw new RuntimeException("You must set parent bounds in populateNodeForItem()"); } // Set the visibility based on the parent bound. if (intersectVisibleToUser(mTempParentRect)) { node.setVisibleToUser(true); node.setBoundsInParent(mTempParentRect); } // Calculate screen-relative bound. mParentView.getLocationOnScreen(mTempGlobalRect); final int offsetX = mTempGlobalRect[0]; final int offsetY = mTempGlobalRect[1]; mTempScreenRect.set(mTempParentRect); mTempScreenRect.offset(offsetX, offsetY); node.setBoundsInScreen(mTempScreenRect); return node; }
private void handleFunctorStatus(View.AttachInfo attachInfo, int status) { // If the draw flag is set, functors will be invoked while executing // the tree of display lists if ((status & DisplayList.STATUS_DRAW) != 0) { if (mRedrawClip.isEmpty()) { attachInfo.mViewRootImpl.invalidate(); } else { attachInfo.mViewRootImpl.invalidateChildInParent(null, mRedrawClip); mRedrawClip.setEmpty(); } } if ((status & DisplayList.STATUS_INVOKE) != 0) { scheduleFunctors(attachInfo, true); } }
private AccessibilityNodeInfoCompat createNodeForChild(int i) { AccessibilityNodeInfoCompat accessibilitynodeinfocompat = AccessibilityNodeInfoCompat.obtain(); accessibilitynodeinfocompat.setEnabled(true); accessibilitynodeinfocompat.setClassName(DEFAULT_CLASS_NAME); onPopulateNodeForVirtualView(i, accessibilitynodeinfocompat); if (accessibilitynodeinfocompat.getText() == null && accessibilitynodeinfocompat.getContentDescription() == null) { throw new RuntimeException( "Callbacks must add text or a content description in populateNodeForVirtualViewId()"); } accessibilitynodeinfocompat.getBoundsInParent(mTempParentRect); if (mTempParentRect.isEmpty()) { throw new RuntimeException( "Callbacks must set parent bounds in populateNodeForVirtualViewId()"); } int j = accessibilitynodeinfocompat.getActions(); if ((j & 0x40) != 0) { throw new RuntimeException( "Callbacks must not add ACTION_ACCESSIBILITY_FOCUS in populateNodeForVirtualViewId()"); } if ((j & 0x80) != 0) { throw new RuntimeException( "Callbacks must not add ACTION_CLEAR_ACCESSIBILITY_FOCUS in populateNodeForVirtualViewId()"); } accessibilitynodeinfocompat.setPackageName(mView.getContext().getPackageName()); accessibilitynodeinfocompat.setSource(mView, i); accessibilitynodeinfocompat.setParent(mView); if (mFocusedVirtualViewId == i) { accessibilitynodeinfocompat.setAccessibilityFocused(true); accessibilitynodeinfocompat.addAction(128); } else { accessibilitynodeinfocompat.setAccessibilityFocused(false); accessibilitynodeinfocompat.addAction(64); } if (intersectVisibleToUser(mTempParentRect)) { accessibilitynodeinfocompat.setVisibleToUser(true); accessibilitynodeinfocompat.setBoundsInParent(mTempParentRect); } mView.getLocationOnScreen(mTempGlobalRect); i = mTempGlobalRect[0]; j = mTempGlobalRect[1]; mTempScreenRect.set(mTempParentRect); mTempScreenRect.offset(i, j); accessibilitynodeinfocompat.setBoundsInScreen(mTempScreenRect); return accessibilitynodeinfocompat; }
private void updatePath() { if (bounds.isEmpty()) return; path = null; RectF outerRect = TiUIHelper.insetRect(boundsF, mPadding); if (radius != null) { path = new Path(); path.setFillType(FillType.EVEN_ODD); if (pathWidth > 0) { path.addRoundRect(outerRect, radius, Direction.CW); float padding = 0; float maxPadding = 0; RectF innerRect = new RectF(); maxPadding = Math.min(bounds.width() / 2, bounds.height() / 2); padding = Math.min(pathWidth, maxPadding); innerRect.set( outerRect.left + padding, outerRect.top + padding, outerRect.right - padding, outerRect.bottom - padding); path.addRoundRect(innerRect, innerRadiusFromPadding(outerRect, padding), Direction.CCW); } else { // adjustment not see background under border because of antialias path.addRoundRect(TiUIHelper.insetRect(outerRect, 0.3f), radius, Direction.CW); } } else { if (pathWidth > 0) { path = new Path(); path.setFillType(FillType.EVEN_ODD); path.addRect(outerRect, Direction.CW); int padding = 0; int maxPadding = 0; RectF innerRect = new RectF(); maxPadding = (int) Math.min(bounds.width() / 2, bounds.height() / 2); padding = (int) Math.min(pathWidth, maxPadding); innerRect.set( outerRect.left + padding, outerRect.top + padding, outerRect.right - padding, outerRect.bottom - padding); path.addRect(innerRect, Direction.CCW); } } }
/** NOTE: This has to be called within a surface transaction. */ public void drawIfNeeded() { synchronized (mWindowManagerService.mWindowMap) { if (!mInvalidated) { return; } mInvalidated = false; Canvas canvas = null; try { // Empty dirty rectangle means unspecified. if (mDirtyRect.isEmpty()) { mBounds.getBounds(mDirtyRect); } mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth); canvas = mSurface.lockCanvas(mDirtyRect); if (DEBUG_VIEWPORT_WINDOW) { Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect); } } catch (IllegalArgumentException iae) { /* ignore */ } catch (Surface.OutOfResourcesException oore) { /* ignore */ } if (canvas == null) { return; } if (DEBUG_VIEWPORT_WINDOW) { Slog.i(LOG_TAG, "Bounds: " + mBounds); } canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); mPaint.setAlpha(mAlpha); Path path = mBounds.getBoundaryPath(); canvas.drawPath(path, mPaint); mSurface.unlockCanvasAndPost(canvas); if (mAlpha > 0) { mSurfaceControl.show(); } else { mSurfaceControl.hide(); } } }
@Override public void getOutline(@NonNull Outline outline) { final Rect bounds = getBounds(); if (bounds.isEmpty()) return; if (mNinePatchState != null) { NinePatch.InsetStruct insets = mNinePatchState.getBitmap().getNinePatchInsets(); if (insets != null) { final Rect outlineInsets = insets.outlineRect; outline.setRoundRect( bounds.left + outlineInsets.left, bounds.top + outlineInsets.top, bounds.right - outlineInsets.right, bounds.bottom - outlineInsets.bottom, insets.outlineRadius); outline.setAlpha(insets.outlineAlpha * (getAlpha() / 255.0f)); return; } } super.getOutline(outline); }
@Implementation public boolean getGlobalVisibleRect(Rect rect, Point globalOffset) { if (globalVisibleRect == null) { /* * The global visible rect is not initialized. The value is not reliable as Robolectric does * not perform layouts in most cases. Use a substitute concept of visibility if no rect * had been set explicitly. */ rect.setEmpty(); return realView.isShown(); } if (!globalVisibleRect.isEmpty()) { rect.set(globalVisibleRect); if (globalOffset != null) { rect.offset(-globalOffset.x, -globalOffset.y); } return true; } rect.setEmpty(); return false; }
private boolean intersectVisibleToUser(Rect rect) { if (rect == null || rect.isEmpty()) { return false; } if (mView.getWindowVisibility() != 0) { return false; } Object obj; for (obj = mView.getParent(); obj instanceof View; obj = ((View) (obj)).getParent()) { obj = (View) obj; if (ViewCompat.getAlpha(((View) (obj))) <= 0.0F || ((View) (obj)).getVisibility() != 0) { return false; } } if (obj == null) { return false; } if (!mView.getLocalVisibleRect(mTempVisibleRect)) { return false; } else { return rect.intersect(mTempVisibleRect); } }
@Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mDownY = ev.getY(); super.onTouchEvent(ev); break; case MotionEvent.ACTION_MOVE: if (mCurrentOffset <= 0) { isNeedMove(); if (!isTouchOne) { startY = ev.getY(); isTouchOne = true; } distance = ev.getY() - startY; if (distance > 0) { isBig = true; setT((int) -distance / 4); } } else { final float preY = mDownY == 0 ? ev.getY() : mDownY; float currentY = ev.getY(); int deltaY = (int) (preY - currentY); // 滚动 mDownY = currentY; // 当滚动到最上或者最下时就不会再滚动,这时移动布局 if (isNeedMove()) { if (normal.isEmpty()) { Log.e( "normal", mFirstChild.getLeft() + " " + mFirstChild.getTop() + " i " + mFirstChild.getBottom() + " " + mFirstChild.getRight() + " " + mFirstChild.getHeight()); // 保存正常的布局位置 normal.set( mFirstChild.getLeft(), mFirstChild.getTop(), mFirstChild.getRight(), mFirstChild.getBottom()); } // 移动布局(关键) mFirstChild.layout( mFirstChild.getLeft(), mFirstChild.getTop() - deltaY / 4, mFirstChild.getRight(), mFirstChild.getBottom() - deltaY / 4); // mFirstChild.scrollBy(0,deltaY); } else { super.onTouchEvent(ev); } } break; case MotionEvent.ACTION_UP: if (isBig) { reset(); isBig = false; } isTouchOne = false; mDownY = 0; if (isNeedAnimation()) { animation(); } super.onTouchEvent(ev); break; } return super.onTouchEvent(ev); }
@Override public boolean onTouchEvent(MotionEvent event) { if (!isEnabled()) return false; final View parent = (View) getParent(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { // only start tracking when in sweet spot final boolean acceptDrag; final boolean acceptLabel; if (mFollowAxis == VERTICAL) { acceptDrag = event.getX() > getWidth() - (mSweepPadding.right * 8); acceptLabel = mLabelLayout != null ? event.getX() < mLabelLayout.getWidth() : false; } else { acceptDrag = event.getY() > getHeight() - (mSweepPadding.bottom * 8); acceptLabel = mLabelLayout != null ? event.getY() < mLabelLayout.getHeight() : false; } final MotionEvent eventInParent = event.copy(); eventInParent.offsetLocation(getLeft(), getTop()); // ignore event when closer to a neighbor for (ChartSweepView neighbor : mNeighbors) { if (isTouchCloserTo(eventInParent, neighbor)) { return false; } } if (acceptDrag) { if (mFollowAxis == VERTICAL) { mTrackingStart = getTop() - mMargins.top; } else { mTrackingStart = getLeft() - mMargins.left; } mTracking = event.copy(); mTouchMode = MODE_DRAG; // starting drag should activate entire chart if (!parent.isActivated()) { parent.setActivated(true); } return true; } else if (acceptLabel) { mTouchMode = MODE_LABEL; return true; } else { mTouchMode = MODE_NONE; return false; } } case MotionEvent.ACTION_MOVE: { if (mTouchMode == MODE_LABEL) { return true; } getParent().requestDisallowInterceptTouchEvent(true); // content area of parent final Rect parentContent = getParentContentRect(); final Rect clampRect = computeClampRect(parentContent); if (clampRect.isEmpty()) return true; long value; if (mFollowAxis == VERTICAL) { final float currentTargetY = getTop() - mMargins.top; final float requestedTargetY = mTrackingStart + (event.getRawY() - mTracking.getRawY()); final float clampedTargetY = MathUtils.constrain(requestedTargetY, clampRect.top, clampRect.bottom); setTranslationY(clampedTargetY - currentTargetY); value = mAxis.convertToValue(clampedTargetY - parentContent.top); } else { final float currentTargetX = getLeft() - mMargins.left; final float requestedTargetX = mTrackingStart + (event.getRawX() - mTracking.getRawX()); final float clampedTargetX = MathUtils.constrain(requestedTargetX, clampRect.left, clampRect.right); setTranslationX(clampedTargetX - currentTargetX); value = mAxis.convertToValue(clampedTargetX - parentContent.left); } // round value from drag to nearest increment value -= value % mDragInterval; setValue(value); dispatchOnSweep(false); return true; } case MotionEvent.ACTION_UP: { if (mTouchMode == MODE_LABEL) { performClick(); } else if (mTouchMode == MODE_DRAG) { mTrackingStart = 0; mTracking = null; mValue = mLabelValue; dispatchOnSweep(true); setTranslationX(0); setTranslationY(0); requestLayout(); } mTouchMode = MODE_NONE; return true; } default: { return false; } } }
/** * 执行移动动画 * * @param event */ private void doActionMove(MotionEvent event) { // 当滚动到顶部时,将状态设置为正常,避免先向上拖动再向下拖动到顶端后首次触摸不响应的问题 if (getScrollY() == 0) { mState = State.NORMAL; // 滑动经过顶部初始位置时,修正Touch down的坐标为当前Touch点的坐标 if (isTop) { isTop = false; mTouchDownY = event.getY(); } } float deltaY = event.getY() - mTouchDownY; // 对于首次Touch操作要判断方位:UP OR DOWN if (deltaY < 0 && mState == State.NORMAL) { mState = State.UP; } else if (deltaY > 0 && mState == State.NORMAL) { mState = State.DOWN; } if (mState == State.UP) { deltaY = deltaY < 0 ? deltaY : 0; isMoving = false; mEnableTouch = false; } else if (mState == State.DOWN) { if (getScrollY() <= deltaY) { mEnableTouch = true; isMoving = true; } deltaY = deltaY < 0 ? 0 : deltaY; } if (isMoving) { // 初始化content view矩形 if (mContentRect.isEmpty()) { // 保存正常的布局位置 mContentRect.set( mContentView.getLeft(), mContentView.getTop(), mContentView.getRight(), mContentView.getBottom()); } // 计算header移动距离(手势移动的距离*阻尼系数*0.5) float headerMoveHeight = deltaY * 0.5f * SCROLL_RATIO; mCurrentTop = (int) (mInitTop + headerMoveHeight); mCurrentBottom = (int) (mInitBottom + headerMoveHeight); // 计算content移动距离(手势移动的距离*阻尼系数) float contentMoveHeight = deltaY * SCROLL_RATIO; // 修正content移动的距离,避免超过header的底边缘 int headerBottom = mCurrentBottom - mHeaderVisibleHeight; int top = (int) (mContentRect.top + contentMoveHeight); int bottom = (int) (mContentRect.bottom + contentMoveHeight); if (top <= headerBottom) { // 移动content view mContentView.layout(mContentRect.left, top, mContentRect.right, bottom); // 移动header view mHeader.layout(mHeader.getLeft(), mCurrentTop, mHeader.getRight(), mCurrentBottom); } } }
/** 是否需要开启动画 */ private boolean isNeedAnimation() { return !mContentRect.isEmpty() && isMoving; }
private void updateGeometry() { final Rect b = getBounds(); mStrokePath.reset(); mFillPath.reset(); mIndicatorFillPath.reset(); mIndicatorStrokePath.reset(); if (b.isEmpty()) { return; } final boolean isHorizontal = mIndicatorDirection == IndicatorDirection.TOP || mIndicatorDirection == IndicatorDirection.BOTTOM; final int innerCornerWidth = mCornerRadius - mStrokeWidth; final float sW = mStrokeWidth; final float iW = mIndicatorWidth; final float cR = mCornerRadius; final float iH = mIndicatorHeight; final float iVISO = iW * (iH + sW) / 2f / iH - iW / 2f; mIndicatorVerticalInnerStrokeOffset = iVISO; if (mIndicatorHeight > 0) { mIndicatorVerticalStrokeWidth = (float) (sW / Math.cos(Math.atan(iW / 2f / iH))); } final float iVSW = mIndicatorVerticalStrokeWidth; final RectF outerRect = new RectF(b.left, b.top, b.right, b.bottom); final RectF innerRect = new RectF(b.left + sW, b.top + sW, b.right - sW, b.bottom - sW); float neededSpace = iW + Math.max(iVISO + Math.max(sW, cR), iVSW + cR) * 2; // correct bubble stroke and fill rectangle so the indicator fits besides if (mIndicatorWidth > 0) { mIndicatorHorizontalStrokeWidth = 2f * iVSW * iH / iW; float addOffset = iH + mIndicatorHorizontalStrokeWidth; switch (mIndicatorDirection) { case LEFT: outerRect.left += addOffset; innerRect.left += addOffset; break; case RIGHT: outerRect.right -= addOffset; innerRect.right -= addOffset; break; case TOP: outerRect.top += addOffset; innerRect.top += addOffset; break; case BOTTOM: outerRect.bottom -= addOffset; innerRect.bottom -= addOffset; break; } } // only create indicator paths if there is room for it since the bounds might be to little // and/or the corners might need the room so there's not enough room for a indicator if (mIndicatorHeight > 0 && mIndicatorWidth > 0 && (!isHorizontal && b.height() >= neededSpace || isHorizontal && b.width() >= neededSpace)) { // would throw an exception on indicator width == 0 // create indicator stroke and fill path based on the given direction switch (mIndicatorDirection) { case LEFT: updateLeftIndicator(outerRect, innerRect); break; case RIGHT: // outerRect.right -= mIndicatorHeight; // innerRect.right -= mIndicatorHeight; // // mIndicatorFillPath.moveTo(outerRect.right, outerRect.top + mStrokeWidth); // mIndicatorFillPath.lineTo(outerRect.right + mIndicatorHeight, // outerRect.top + mStrokeWidth + mIndicatorWidth / 2f); // mIndicatorFillPath.lineTo(outerRect.right, // outerRect.top + mStrokeWidth + mIndicatorWidth); // mIndicatorFillPath.close(); break; case TOP: // outerRect.top += mIndicatorHeight; // innerRect.top += mIndicatorHeight; // // mIndicatorFillPath.moveTo(outerRect.left + mStrokeWidth, outerRect.top); // mIndicatorFillPath.lineTo(outerRect.left + mStrokeWidth + mIndicatorWidth / 2f, // outerRect.top - mIndicatorHeight); // mIndicatorFillPath.lineTo(outerRect.left + mStrokeWidth + mIndicatorWidth, // outerRect.top); // mIndicatorFillPath.close(); // // mIndicatorFillPath.setLastPoint(outerRect.left, outerRect.top); // mIndicatorFillPath.lineTo(outerRect.left + mIndicatorHeight, // outerRect.top + mStrokeWidth + mIndicatorWidth / 2f); // mIndicatorFillPath.close(); break; case BOTTOM: // outerRect.bottom -= mIndicatorHeight; // innerRect.bottom -= mIndicatorHeight; // // mIndicatorFillPath.moveTo(outerRect.left + mStrokeWidth, outerRect.bottom); // mIndicatorFillPath.lineTo(outerRect.left + mStrokeWidth + mIndicatorWidth / 2f, // outerRect.bottom + mIndicatorHeight); // mIndicatorFillPath.lineTo(outerRect.left + mStrokeWidth + mIndicatorWidth, // outerRect.bottom); // mIndicatorFillPath.close(); break; } } // that's the main fill and stroke path of the bubble if (mCornerRadius >= 1) { // create bubble rect with round corners float[] outerRadius = new float[8]; Arrays.fill(outerRadius, mCornerRadius); mStrokePath.addRoundRect(outerRect, outerRadius, Direction.CW); if (innerCornerWidth >= 1) { float[] innerRadius = new float[8]; Arrays.fill(innerRadius, innerCornerWidth); if (mStrokeWidth >= 1) { mStrokePath.addRoundRect(innerRect, innerRadius, Direction.CCW); } mFillPath.addRoundRect(innerRect, innerRadius, Direction.CW); } else { if (mStrokeWidth >= 1) { mStrokePath.addRect(innerRect, Direction.CCW); } mFillPath.addRect(innerRect, Direction.CW); } } else { // create bubble without round corners if (mStrokeWidth >= 1) { mStrokePath.addRect(outerRect, Direction.CW); mStrokePath.addRect(innerRect, Direction.CCW); } mFillPath.addRect(innerRect, Direction.CW); } }
@Override public void onDraw(Canvas canvas) { super.onDraw(canvas); onDrawExcelBackgroud(canvas); if (colWidth == null || rowHeight == null) return; if (showLinkedLine) onDrawLinkedLines(canvas); for (Cell cell : cells) { Rect rect = getCellClipRect(cell); if (rect != null) { canvas.save(); canvas.clipRect(rect); onDrawCell(canvas, cell); canvas.restore(); } } // 根据合并单元格设置裁剪区域 canvas.save(); canvas.clipRect(0, 0, this.getWidth(), this.getHeight()); int fixMergedIndex = 0; for (Cell cell : mergedCells) { int tx = offX, ty = offY; if (cell.fixedX) tx = 0; if (cell.fixedY) ty = 0; int x0 = tx + cellX[cell.col]; int y0 = ty + cellY[cell.row]; int x1 = tx + cellX[cell.col + cell.width]; int y1 = ty + cellY[cell.row + cell.height]; if (x0 > this.getWidth() || y0 > this.getHeight()) continue; if (x1 < 0 || y1 < 0) continue; canvas.clipRect(x0 + 1, y0 + 1, x1 - 1, y1 - 1, Op.DIFFERENCE); if (cell.fixedX || cell.fixedY) { if (fixMergedIndex >= fixMerged.size()) fixMerged.add(new Rect(0, 0, -1, -1)); Rect r = fixMerged.get(fixMergedIndex); r.set(x0 + 1, y0 + 1, x1 - 1, y1 - 1); fixMergedIndex++; } } if (fixedRow > 0 && fixedRow < cellY.length) { if (Build.VERSION.SDK_INT > 11) // 2.3个别版本使用Op.UNION后会导致绘制到上层View canvas.clipRect(0, 0, this.getWidth(), cellY[fixedRow], Op.UNION); } if (fixedCol > 0 && fixedCol < cellX.length) { if (Build.VERSION.SDK_INT > 11) // 2.3个别版本使用Op.UNION后会导致绘制到上层View canvas.clipRect(0, 0, cellX[fixedCol], this.getHeight(), Op.UNION); } for (int i = 0; i < fixMergedIndex; i++) { Rect r = fixMerged.get(i); if (r.isEmpty() || r.left > this.getWidth() || r.top > this.getHeight()) continue; if (r.right < 0 || r.bottom < 0) continue; canvas.clipRect(r.left, r.top, r.right, r.bottom, Op.DIFFERENCE); } painter.setStrokeWidth(1); int tempFirst = -1; int tempLast = rowHeight.length; if (this.horizontalDivideLineColor != null) painter.setColor(horizontalDivideLineColor); // 绘制分割线 for (int i = 0; i < cellY.length; i++) { if (offY + cellY[i] >= 0 && tempFirst == -1) // 记录第一个可见行 tempFirst = i; if (i <= this.fixedRow) { if (horizontalDivideLineColor != null && horizontalDivideLineColor != Color.TRANSPARENT) canvas.drawLine(offX, cellY[i], offX + worldWidth, cellY[i], painter); } else { int minY = 0; if (this.fixedRow > 0) minY = cellY[fixedRow]; if (offY + cellY[i] < minY) continue; if (offY + cellY[i] > this.getHeight()) break; tempLast = i; // 记录最后一个可见行 if (horizontalDivideLineColor != null && horizontalDivideLineColor != Color.TRANSPARENT) canvas.drawLine(offX, offY + cellY[i], offX + worldWidth, offY + cellY[i], painter); } } this.firstVisibleRow = tempFirst; this.lastVisibleRow = tempLast; tempFirst = -1; tempLast = this.colWidth.length - 1; if (this.verticalDivideLineColor != null) painter.setColor(verticalDivideLineColor); for (int i = 0; i < cellX.length; i++) { if (offX + cellX[i] >= 0 && tempFirst == -1) tempFirst = i; if (i <= this.fixedCol) { if (this.verticalDivideLineColor != null && verticalDivideLineColor != Color.TRANSPARENT) canvas.drawLine(cellX[i], offY, cellX[i], offY + worldHeight, painter); } else { int minX = 0; if (this.fixedCol > 0) minX = cellX[fixedCol]; if (offX + cellX[i] < minX) continue; if (offX + cellX[i] > this.getWidth()) break; tempLast = i; if (verticalDivideLineColor != null && verticalDivideLineColor != Color.TRANSPARENT) canvas.drawLine(offX + cellX[i], offY, offX + cellX[i], offY + worldHeight, painter); } } this.firstVisibleCol = tempFirst; this.lastVisibleCol = tempLast; canvas.restore(); }
private void fixCurrentRectBounds() { if (mCurrentRect == null || mCurrentRect.isEmpty()) return; if (mCurrentRect.left < 0) mCurrentRect.offset(-mCurrentRect.left, 0); if (mCurrentRect.top < 0) mCurrentRect.offset(0, -mCurrentRect.top); }
// 是否需要开启动画 public boolean isNeedAnimation() { return !normal.isEmpty(); }