/** Resets the dirty region when the motion event occurs. */
  private void resetDirtyRect(float eventX, float eventY) {

    // The lastTouchX and lastTouchY were set when the ACTION_DOWN
    // motion event occurred.
    dirtyRect.left = Math.min(lastTouchX, eventX);
    dirtyRect.right = Math.max(lastTouchX, eventX);
    dirtyRect.top = Math.min(lastTouchY, eventY);
    dirtyRect.bottom = Math.max(lastTouchY, eventY);
  }
  private void adjustFirstCellsAndScroll() {
    int values[];

    //		values = adjustFirstCellsAndScroll(scrollX, firstColumn, widths);
    //		scrollX = values[0];
    //		firstColumn = values[1];
    // 切换应用导致重新布局时横向滚到最前端
    scrollX = 0;
    firstColumn = 0;
    sumX = 0;

    values = adjustFirstCellsAndScroll(scrollY, firstRow, heights);
    scrollY = values[0];
    firstRow = values[1];

    if (lineView != null) {
      int maxY = lineView.getBottom() - height + lineView.getTop();
      // data变化后maxY发生变化,而之前存的sumY也失效了,滑动时以现在的maxY为准
      if (maxY < 0) {
        maxY = 0;
      }
      sumY = Math.min(maxY, sumY);
      lineView.scrollBy(0, sumY);
    }
  }
  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    //        Logger.d(TAG, String.format("onSizeChanged, w: %s, h: %s, oldw: %s, oldh: %s", w, h,
    // oldw, oldh));
    if (isFirst && w > 0 && h > 0) {
      isFirst = false;

      originWidth = w;
      originHeight = h;

      originRadius = Math.min(originWidth, originHeight) / 2;
      curRadius = originRadius;
      touchedPointRadius = originRadius;

      maxMoveLength = ABAppUtil.getDeviceHeight(context) / 6;

      refreshStartPoint();

      ViewGroup.LayoutParams lp = this.getLayoutParams();
      if (RelativeLayout.LayoutParams.class.isAssignableFrom(lp.getClass())) {
        originLp = (RelativeLayout.LayoutParams) lp;
      }
      newLp = new RelativeLayout.LayoutParams(lp.width, lp.height);
    }
  }
  private void computePath(Rect bounds) {
    final float currentScale = mCurrentScale;
    final Path path = mPath;
    final RectF rect = mRect;
    final Matrix matrix = mMatrix;

    path.reset();
    int totalSize = Math.min(bounds.width(), bounds.height());

    float initial = mClosedStateSize;
    float destination = totalSize;
    float currentSize = initial + (destination - initial) * currentScale;

    float halfSize = currentSize / 2f;
    float inverseScale = 1f - currentScale;
    float cornerSize = halfSize * inverseScale;
    float[] corners =
        new float[] {
          halfSize, halfSize, halfSize, halfSize, halfSize, halfSize, cornerSize, cornerSize
        };
    rect.set(bounds.left, bounds.top, bounds.left + currentSize, bounds.top + currentSize);
    path.addRoundRect(rect, corners, Path.Direction.CCW);
    matrix.reset();
    matrix.postRotate(-45, bounds.left + halfSize, bounds.top + halfSize);
    matrix.postTranslate((bounds.width() - currentSize) / 2, 0);
    float hDiff = (bounds.bottom - currentSize - mExternalOffset) * inverseScale;
    matrix.postTranslate(0, hDiff);
    path.transform(matrix);
  }
  // Grows the cropping rectange by (dx, dy) in image space.
  void moveBy(float dx, float dy) {

    Rect invalRect = new Rect(mDrawRect);

    mCropRect.offset(dx, dy);

    // Put the cropping rectangle inside image rectangle.
    mCropRect.offset(
        Math.max(0, mImageRect.left - mCropRect.left), Math.max(0, mImageRect.top - mCropRect.top));

    mCropRect.offset(
        Math.min(0, mImageRect.right - mCropRect.right),
        Math.min(0, mImageRect.bottom - mCropRect.bottom));

    mDrawRect = computeLayout();
    invalRect.union(mDrawRect);
    invalRect.inset(-10, -10);
    mContext.invalidate(invalRect);
  }
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int measuredWidth = measure(widthMeasureSpec);
    int measuredHeight = measure(heightMeasureSpec);

    int d = Math.min(measuredWidth, measuredHeight);

    setMeasuredDimension(d, d);
  }
 /**
  * @param velocity
  * @return the page you should "land" on
  */
 private int getNextPage(int velocity) {
   int nextPage;
   if (velocity > mMinimumVelocity) {
     nextPage = getCurrentPageFloor();
   } else if (velocity < -mMinimumVelocity) {
     nextPage = getCurrentPageCeil();
   } else {
     nextPage = getCurrentPageRound();
   }
   return Math.min(Math.max(nextPage, 0), mPageCount - 1);
 }
示例#8
0
 public static int calculateInSampleSize(
     BitmapFactory.Options options, int reqWidth, int reqHeight) {
   int h = options.outHeight;
   int w = options.outWidth;
   int inSampleSize = 0;
   if (h > reqHeight || w > reqWidth) {
     float ratioW = (float) w / reqWidth;
     float ratioH = (float) h / reqHeight;
     inSampleSize = (int) Math.min(ratioH, ratioW);
   }
   inSampleSize = Math.max(1, inSampleSize);
   return inSampleSize;
 }
  private void shadowsVisibility() {
    final int actualScrollX = getActualScrollX();
    final int actualScrollY = getActualScrollY();
    final int[] remainPixels = {
      actualScrollX,
      actualScrollY,
      getMaxScrollX() - actualScrollX,
      getMaxScrollY() - actualScrollY,
    };

    for (int i = 0; i < shadows.length; i++) {
      setAlpha(shadows[i], Math.min(remainPixels[i] / (float) shadowSize, 1));
    }
  }
 private int scrollBounds(int desiredScroll, int firstCell, int sizes[], int viewSize) {
   if (desiredScroll == 0) {
     // no op
   } else if (desiredScroll < 0) {
     desiredScroll = Math.max(desiredScroll, -sumArray(sizes, 1, firstCell));
   } else {
     desiredScroll =
         Math.min(
             desiredScroll,
             Math.max(
                 0,
                 sumArray(sizes, firstCell + 1, sizes.length - 1 - firstCell)
                     + sizes[0]
                     - viewSize));
   }
   return desiredScroll;
 }
  /**
   * Determines the specs for the onMeasure function. Calculates the width or height depending on
   * the mode.
   *
   * @param measureSpecMode The mode of the measured width or height.
   * @param measureSpecSize The size of the measured width or height.
   * @param desiredSize The desired size of the measured width or height.
   * @return The final size of the width or height.
   */
  private static int getOnMeasureSpec(int measureSpecMode, int measureSpecSize, int desiredSize) {

    // Measure Width
    int spec;
    if (measureSpecMode == MeasureSpec.EXACTLY) {
      // Must be this size
      spec = measureSpecSize;
    } else if (measureSpecMode == MeasureSpec.AT_MOST) {
      // Can't be bigger than...; match_parent value
      spec = Math.min(desiredSize, measureSpecSize);
    } else {
      // Be whatever you want; wrap_content
      spec = desiredSize;
    }

    return spec;
  }
  private void drawSky(Canvas canvas) {
    Matrix matrix = mMatrix;
    matrix.reset();
    int y = Math.max(0, mTop - mTotalDragDistance);

    //  0  ~ 1
    float dragPercent = Math.min(1f, Math.abs(mPercent));

    /**
     * Change skyScale between {@link #SKY_INITIAL_SCALE} and 1.0f depending on {@link #mPercent}
     */
    // 根据拖动的比例改变天空的大小
    float skyScale;
    float scalePercentDelta = dragPercent - SCALE_START_PERCENT;

    /** less than {@link SCALE_START_PERCENT} will be {@link SKY_INITIAL_SCALE} */
    // 拖动比例小于0.3时使用默认的大小1.05
    if (scalePercentDelta > 0) {
      /** will change from 0 ~ 1 * */
      // skyScale = SKY_INITIAL_SCALE + (dragPercent - SCALE_START_PERCENT);
      float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT);
      skyScale = SKY_INITIAL_SCALE - (SKY_INITIAL_SCALE - 1.0f) * scalePercent;
    } else {
      skyScale = SKY_INITIAL_SCALE;
    }

    float offsetX = -(mScreenWidth * skyScale - mScreenWidth) / 2.0f;

    float offsetY =
        y
            + 50
            + mSkyTopOffset // Offset canvas moving, goes lower when goes down
            - mSkyHeight
                * (skyScale - 1.0f)
                / 2 // Offset sky scaling, lower than 0, will go greater when goes down
            + mSkyMoveOffset
                * dragPercent; // Give it a little move top -> bottom  // will go greater when goes
                               // down

    matrix.postScale(skyScale, skyScale);
    matrix.postTranslate(offsetX, offsetY);
    canvas.drawBitmap(mSky, matrix, null);
  }
  private void drawTown(Canvas canvas) {
    Matrix matrix = mMatrix;
    matrix.reset();

    int y = Math.max(0, mTop - mTotalDragDistance);
    float dragPercent = Math.min(1f, Math.abs(mPercent));

    float townScale;
    float townTopOffset;
    float townMoveOffset;
    float scalePercentDelta = dragPercent - SCALE_START_PERCENT;
    if (scalePercentDelta > 0) {
      /**
       * Change townScale between {@link #TOWN_INITIAL_SCALE} and {@link #TOWN_FINAL_SCALE}
       * depending on {@link #mPercent} Change townTopOffset between {@link #mTownInitialTopOffset}
       * and {@link #mTownFinalTopOffset} depending on {@link #mPercent}
       */
      float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT);
      townScale = TOWN_INITIAL_SCALE + (TOWN_FINAL_SCALE - TOWN_INITIAL_SCALE) * scalePercent;
      townTopOffset =
          mTownInitialTopOffset - (mTownFinalTopOffset - mTownInitialTopOffset) * scalePercent;
      townMoveOffset = mTownMoveOffset * (1.0f - scalePercent);
    } else {
      float scalePercent = dragPercent / SCALE_START_PERCENT;
      townScale = TOWN_INITIAL_SCALE;
      townTopOffset = mTownInitialTopOffset;
      townMoveOffset = mTownMoveOffset * scalePercent;
    }

    float offsetX = -(mScreenWidth * townScale - mScreenWidth) / 2.0f;
    // float offsetY = (1.0f - dragPercent) * mTotalDragDistance // Offset canvas moving
    float offsetY =
        y
            + +townTopOffset
            - mTownHeight * (townScale - 1.0f) / 2 // Offset town scaling
            + townMoveOffset; // Give it a little move

    matrix.postScale(townScale, townScale);
    // 移动建筑的中心点
    matrix.postTranslate(offsetX, offsetY);

    canvas.drawBitmap(mTown, matrix, null);
  }
示例#14
0
  @Override
  public void draw(Canvas canvas) {
    super.draw(canvas);
    Rect r = getBounds();

    // draw border
    if (borderThickness > 0) {
      drawBorder(canvas);
    }

    int count = canvas.save();
    canvas.translate(r.left, r.top);

    // draw text
    int width = this.width < 0 ? r.width() : this.width;
    int height = this.height < 0 ? r.height() : this.height;
    int fontSize = this.fontSize < 0 ? (Math.min(width, height) / 2) : this.fontSize;
    textPaint.setTextSize(fontSize);
    canvas.drawText(
        text, width / 2, height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2), textPaint);

    canvas.restoreToCount(count);
  }
  private void dataSetChanged() {
    final int currentPage = mCurrentPageIndex;
    int newPosition = currentPage;

    // if the adapter has stable ids, try to keep the page currently on
    // stable.
    if (mAdapter.hasStableIds() && currentPage != INVALID_PAGE_POSITION) {
      newPosition = getNewPositionOfCurrentPage();
    } else if (currentPage == INVALID_PAGE_POSITION) {
      newPosition = 0;
    }

    // remove all the current views
    recycleActiveViews();
    mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
    mRecycler.invalidateScraps();

    mPageCount = mAdapter.getCount();

    // put the current page within the new adapter range
    newPosition = Math.min(mPageCount - 1, newPosition == INVALID_PAGE_POSITION ? 0 : newPosition);

    if (newPosition != INVALID_PAGE_POSITION) {
      // TODO pretty confusing
      // this will be correctly set in setFlipDistance method
      mCurrentPageIndex = INVALID_PAGE_POSITION;
      mFlipDistance = INVALID_FLIP_DISTANCE;
      flipTo(newPosition);
    } else {
      mFlipDistance = INVALID_FLIP_DISTANCE;
      mPageCount = 0;
      setFlipDistance(0);
    }

    updateEmptyStatus();
  }
示例#16
0
    public void build(DialogWireframe description, int w, int h, TelegramApplication application) {
      layoutH = h;
      layoutW = w;

      if (description.getPeerType() == PeerType.PEER_USER) {
        if (description.getPeerId() == 333000) {
          isHighlighted = false;
        } else {
          User user = description.getDialogUser();
          isHighlighted = user.getLinkType() == LinkType.FOREIGN;
        }
        isGroup = false;
        isEncrypted = false;
      } else if (description.getPeerType() == PeerType.PEER_CHAT) {
        isHighlighted = false;
        isGroup = true;
        isEncrypted = false;
      } else if (description.getPeerType() == PeerType.PEER_USER_ENCRYPTED) {
        isHighlighted = false;
        isGroup = false;
        isEncrypted = true;
      }

      isBodyHighlighted = description.getContentType() != ContentType.MESSAGE_TEXT;

      if (description.getUnreadCount() != 0 && !description.isMine()) {
        isUnreadIn = true;
      } else {
        isUnreadIn = false;
      }

      time = org.telegram.android.ui.TextUtil.formatDate(description.getDate(), application);
      isRtl = application.isRTL();

      if (IS_LARGE) {
        layoutAvatarWidth = px(64);
        layoutPadding = application.getResources().getDimensionPixelSize(R.dimen.dialogs_padding);
        layoutBodyPadding = layoutAvatarWidth + layoutPadding + px(12);
        layoutAvatarTop = px(8);
        layoutTitleTop = px(34);
        layoutMainTop = px(60);
        layoutTimeTop = px(34);

        layoutMarkTop = px(44);
        layoutMarkBottom = layoutMarkTop + px(22);
        layoutMarkTextTop = layoutMarkTop + px(18);
      } else {
        layoutAvatarWidth = px(54);
        layoutPadding = application.getResources().getDimensionPixelSize(R.dimen.dialogs_padding);
        layoutBodyPadding = layoutAvatarWidth + layoutPadding + px(12);
        layoutAvatarTop = px(8);
        layoutTitleTop = px(30);
        layoutMainTop = px(54);
        layoutTimeTop = px(30);

        layoutMarkTop = px(38);
        layoutMarkBottom = layoutMarkTop + px(22);
        layoutMarkTextTop = layoutMarkTop + px(18);
      }

      layoutMainContentTop = (int) (layoutMainTop + bodyPaint.getFontMetrics().ascent);
      layoutTitleLayoutTop = (int) (layoutTitleTop + titlePaint.getFontMetrics().ascent);
      layoutStateTop = layoutTimeTop - px(10);
      layoutClockTop = layoutTimeTop - px(12);
      layoutEncryptedTop = layoutTimeTop - px(14);

      if (isRtl) {
        layoutAvatarLeft = w - layoutPadding - layoutAvatarWidth;
      } else {
        layoutAvatarLeft = layoutPadding;
      }

      int timeWidth = (int) unreadClockPaint.measureText(time);
      if (isRtl) {
        layoutTimeLeft = layoutPadding;
        layoutStateLeftDouble = layoutPadding + timeWidth + px(2);
        layoutStateLeft = layoutStateLeftDouble + px(6);
        layoutClockLeft = layoutPadding + timeWidth + px(2);
      } else {
        layoutTimeLeft = w - layoutPadding - timeWidth;
        layoutClockLeft = w - layoutPadding - timeWidth - px(14);
        layoutStateLeft = w - layoutPadding - timeWidth - px(16);
        layoutStateLeftDouble = w - layoutPadding - timeWidth - px(6 + 16);
      }

      layoutMarkRadius = px(2);
      if (description.isErrorState()
          || (description.getMessageState() == MessageState.FAILURE && description.isMine())) {
        layoutMarkWidth = px(22);
        if (isRtl) {
          layoutMarkLeft = layoutPadding; // getMeasuredWidth() - layoutMarkWidth - getPx(80);
        } else {
          layoutMarkLeft = w - layoutMarkWidth - layoutPadding;
        }
      } else {
        if (description.getUnreadCount() > 0) {
          if (description.getUnreadCount() >= 1000) {
            unreadCountText =
                I18nUtil.getInstance().correctFormatNumber(description.getUnreadCount() / 1000)
                    + "K";
          } else {
            unreadCountText =
                I18nUtil.getInstance().correctFormatNumber(description.getUnreadCount());
          }
          int width = (int) counterTitlePaint.measureText(unreadCountText);
          Rect r = new Rect();
          counterTitlePaint.getTextBounds(unreadCountText, 0, unreadCountText.length(), r);
          layoutMarkTextTop =
              layoutMarkTop + (layoutMarkBottom - layoutMarkTop + r.top) / 2 - r.top;
          if (width < px(22 - 14)) {
            layoutMarkWidth = px(22);
          } else {
            layoutMarkWidth = px(14) + width;
          }
          layoutMarkTextLeft = (layoutMarkWidth - width) / 2;

          if (isRtl) {
            layoutMarkLeft = layoutPadding; // getMeasuredWidth() - layoutMarkWidth - getPx(80);
          } else {
            layoutMarkLeft = w - layoutMarkWidth - layoutPadding;
          }
        } else {
          layoutMarkLeft = 0;
          layoutMarkWidth = 0;
        }
      }
      layoutMarkRect.set(
          layoutMarkLeft, layoutMarkTop, layoutMarkLeft + layoutMarkWidth, layoutMarkBottom);

      if (description.getPeerType() == PeerType.PEER_USER_ENCRYPTED) {
        if (isRtl) {
          if (description.isMine()) {
            layoutTitleLeft = timeWidth + px(16) + px(16);
          } else {
            layoutTitleLeft = timeWidth + px(12);
          }
          layoutTitleWidth = w - layoutTitleLeft - layoutBodyPadding - px(14) - px(6);
          layoutEncryptedLeft = w - layoutBodyPadding - px(12);
        } else {
          layoutTitleLeft = layoutBodyPadding + px(16);
          if (description.isMine()) {
            layoutTitleWidth = w - layoutTitleLeft - timeWidth - px(24);
          } else {
            layoutTitleWidth = w - layoutTitleLeft - timeWidth - px(12);
          }

          layoutEncryptedLeft = layoutBodyPadding + px(2);
        }
      } else {
        if (isRtl) {
          if (description.isMine()) {
            layoutTitleLeft = timeWidth + px(16) + px(16);
          } else {
            layoutTitleLeft = timeWidth + px(12);
          }
          layoutTitleWidth = w - layoutTitleLeft - layoutBodyPadding;
        } else {
          layoutTitleLeft = layoutBodyPadding;
          if (description.isMine()) {
            layoutTitleWidth = w - layoutTitleLeft - timeWidth - px(24) - px(12);
          } else {
            layoutTitleWidth = w - layoutTitleLeft - timeWidth - px(12);
          }
        }
      }

      layoutMainWidth = w - layoutBodyPadding - layoutPadding;
      if (isRtl) {
        layoutMainLeft = w - layoutMainWidth - layoutBodyPadding;
        if (layoutMarkWidth != 0) {
          layoutMainLeft += layoutMarkWidth + px(8);
          layoutMainWidth -= layoutMarkWidth + px(8);
        }
      } else {
        layoutMainLeft = layoutBodyPadding;
        if (layoutMarkWidth != 0) {
          layoutMainWidth -= layoutMarkWidth + px(8);
        }
      }

      avatarRect.set(
          layoutAvatarLeft,
          layoutAvatarTop,
          layoutAvatarLeft + layoutAvatarWidth,
          layoutAvatarTop + layoutAvatarWidth);

      // Building text layouts
      {
        String message = description.getMessage();
        if (message.length() > 150) {
          message = message.substring(0, 150) + "...";
        }
        message = message.replace("\n", " ");

        TextPaint bodyTextPaint;
        if (isBodyHighlighted) {
          bodyTextPaint = bodyHighlightPaint;
        } else {
          if (HIGHLIGHT_UNDEAD) {
            if (isUnreadIn) {
              bodyTextPaint = bodyUnreadPaint;
            } else {
              bodyTextPaint = bodyPaint;
            }
          } else {
            bodyTextPaint = bodyPaint;
          }
        }

        int nameLength = 0;

        if (description.getContentType() != ContentType.MESSAGE_SYSTEM) {
          if (description.isMine()) {
            String name = application.getResources().getString(R.string.st_dialog_you);
            nameLength = BidiFormatter.getInstance().unicodeWrap(name).length();
            message =
                BidiFormatter.getInstance().unicodeWrap(name)
                    + ": "
                    + BidiFormatter.getInstance().unicodeWrap(message);
          } else {
            if (isGroup) {
              User user = description.getSender();
              nameLength = BidiFormatter.getInstance().unicodeWrap(user.getFirstName()).length();
              message =
                  BidiFormatter.getInstance().unicodeWrap(user.getFirstName().replace("\n", " "))
                      + ": "
                      + BidiFormatter.getInstance().unicodeWrap(message);
            }
          }
        }

        String preSequence =
            TextUtils.ellipsize(message, bodyTextPaint, layoutMainWidth, TextUtils.TruncateAt.END)
                .toString();

        //                Spannable sequence =
        // application.getEmojiProcessor().processEmojiCutMutable(preSequence,
        // EmojiProcessor.CONFIGURATION_DIALOGS);
        //                if (nameLength != 0) {
        //                    sequence.setSpan(new ForegroundColorSpan(HIGHLIGHT_COLOR), 0,
        // Math.min(nameLength, sequence.length()), Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        //                }
        //                CharSequence resSequence = TextUtils.ellipsize(sequence, bodyTextPaint,
        // layoutMainWidth, TextUtils.TruncateAt.END);
        //                bodyLayout = new StaticLayout(resSequence, bodyTextPaint, layoutMainWidth,
        // Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
        //                bodyString = null;

        Spannable sequence =
            application
                .getEmojiProcessor()
                .processEmojiCutMutable(preSequence, EmojiProcessor.CONFIGURATION_DIALOGS);
        if (nameLength != 0) {
          sequence.setSpan(
              new ForegroundColorSpan(HIGHLIGHT_COLOR),
              0,
              Math.min(nameLength, sequence.length()),
              Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        }

        CharSequence resSequence =
            TextUtils.ellipsize(sequence, bodyTextPaint, layoutMainWidth, TextUtils.TruncateAt.END);
        bodyLayout =
            new StaticLayout(
                resSequence,
                bodyTextPaint,
                layoutMainWidth,
                Layout.Alignment.ALIGN_NORMAL,
                1.0f,
                0.0f,
                false);
        bodyString = null;
        //                if (EmojiProcessor.containsEmoji(message)) {
        //                    Spannable sequence =
        // application.getEmojiProcessor().processEmojiCutMutable(preSequence,
        // EmojiProcessor.CONFIGURATION_DIALOGS);
        //                    if (nameLength != 0) {
        //                        sequence.setSpan(new ForegroundColorSpan(HIGHLIGHT_COLOR), 0,
        // Math.min(nameLength, sequence.length()), Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        //                    }
        //
        //                    CharSequence resSequence = TextUtils.ellipsize(sequence,
        // bodyTextPaint, layoutMainWidth, TextUtils.TruncateAt.END);
        //                    bodyLayout = new StaticLayout(resSequence, bodyTextPaint,
        // layoutMainWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
        //                    bodyString = null;
        //                } else {
        //                    bodyString = preSequence;
        //                    bodyLayout = null;
        //                }
      }

      // Title
      {
        String title = description.getDialogTitle();
        if (title.length() > 150) {
          title = title.substring(150) + "...";
        }
        title = title.replace("\n", " ");

        TextPaint paint =
            isEncrypted ? titleEncryptedPaint : (isHighlighted ? titleHighlightPaint : titlePaint);

        //                Spannable preSequence =
        // application.getEmojiProcessor().processEmojiCutMutable(title,
        // EmojiProcessor.CONFIGURATION_DIALOGS);
        //                CharSequence sequence = TextUtils.ellipsize(preSequence, paint,
        // layoutTitleWidth, TextUtils.TruncateAt.END);
        //                titleLayout = new StaticLayout(sequence, paint, layoutTitleWidth,
        // Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
        //                titleString = null;

        if (EmojiProcessor.containsEmoji(title)) {
          Spannable preSequence =
              application
                  .getEmojiProcessor()
                  .processEmojiCutMutable(title, EmojiProcessor.CONFIGURATION_DIALOGS);
          CharSequence sequence =
              TextUtils.ellipsize(preSequence, paint, layoutTitleWidth, TextUtils.TruncateAt.END);
          titleLayout =
              new StaticLayout(
                  sequence,
                  paint,
                  layoutTitleWidth,
                  Layout.Alignment.ALIGN_NORMAL,
                  1.0f,
                  0.0f,
                  false);
          titleString = null;
        } else {
          titleString =
              TextUtils.ellipsize(title, paint, layoutTitleWidth, TextUtils.TruncateAt.END)
                  .toString();
          titleLayout = null;
        }
      }

      // Placeholder
      placeHolderName = description.getDialogName();
      placeHolderColor = Placeholders.getBgColor(description.getPeerId());

      if (placeHolderName.length() > 0) {
        usePlaceholder = true;
        placeholderLeft = layoutAvatarLeft + layoutAvatarWidth / 2;
        Rect rect = new Rect();
        placeholderTextPaint.getTextBounds(placeHolderName, 0, placeHolderName.length(), rect);
        placeholderTop = layoutAvatarTop + (layoutAvatarWidth / 2 + ((rect.bottom - rect.top) / 2));
      } else {
        usePlaceholder = false;
      }
    }
  @SuppressLint("DrawAllocation")
  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (needRelayout || changed) {
      needRelayout = false;
      resetTable();

      if (adapter != null) {
        width = r - l;
        height = b - t;

        int left, top, right, bottom;

        right = Math.min(width, sumArray(widths));
        bottom = Math.min(height, sumArray(heights));
        addShadow(shadows[0], widths[0], 0, widths[0] + shadowSize, bottom);
        addShadow(shadows[1], 0, heights[0], right, heights[0] + shadowSize);
        addShadow(shadows[2], right - shadowSize, 0, right, bottom);
        addShadow(shadows[3], 0, bottom - shadowSize, right, bottom);

        addLineView(
            widths[0],
            heights[0],
            sumArray(widths, 1, widths.length - 1),
            sumArray(heights, 1, heights.length - 1));

        headView = makeAndSetup(-1, -1, 0, 0, widths[0], heights[0]);

        scrollBounds();
        adjustFirstCellsAndScroll();

        left = widths[0] - scrollX;
        for (int i = firstColumn; i < columnCount && left < width; i++) {
          right = left + widths[i + 1];
          final View view = makeAndSetup(-1, i, left, 0, right, heights[0]);
          rowViewList.add(view);
          left = right;
        }

        top = heights[0] - scrollY;
        for (int i = firstRow; i < rowCount && top < height; i++) {
          bottom = top + heights[i + 1];
          final View view = makeAndSetup(i, -1, 0, top, widths[0], bottom);
          columnViewList.add(view);
          top = bottom;
        }

        top = heights[0] - scrollY;
        for (int i = firstRow; i < rowCount && top < height; i++) {
          bottom = top + heights[i + 1];
          left = widths[0] - scrollX;
          List<View> list = new ArrayList<View>();
          for (int j = firstColumn; j < columnCount && left < width; j++) {
            right = left + widths[j + 1];
            final View view = makeAndSetup(i, j, left, top, right, bottom);
            list.add(view);
            left = right;
          }
          bodyViewTable.add(list);
          top = bottom;
        }

        shadowsVisibility();
      }
    }
  }
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    final int w;
    final int h;

    if (adapter != null) {
      this.rowCount = adapter.getRowCount();
      this.columnCount = adapter.getColumnCount();

      widths = new int[columnCount + 1];
      for (int i = -1; i < columnCount; i++) {
        widths[i + 1] += adapter.getWidth(i);
      }
      heights = new int[rowCount + 1];
      for (int i = -1; i < rowCount; i++) {
        heights[i + 1] += adapter.getHeight(i);
      }

      if (widthMode == MeasureSpec.AT_MOST) {
        w = Math.min(widthSize, sumArray(widths));
      } else if (widthMode == MeasureSpec.UNSPECIFIED) {
        w = sumArray(widths);
      } else {
        w = widthSize;
        int sumArray = sumArray(widths);
        if (sumArray < widthSize) {
          final float factor = widthSize / (float) sumArray;
          for (int i = 1; i < widths.length; i++) {
            widths[i] = Math.round(widths[i] * factor);
          }
          widths[0] = widthSize - sumArray(widths, 1, widths.length - 1);
        }
      }

      if (heightMode == MeasureSpec.AT_MOST) {
        h = Math.min(heightSize, sumArray(heights));
      } else if (heightMode == MeasureSpec.UNSPECIFIED) {
        h = sumArray(heights);
      } else {
        h = heightSize;
      }
    } else {
      if (heightMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
        w = 0;
        h = 0;
      } else {
        w = widthSize;
        h = heightSize;
      }
    }

    if (firstRow >= rowCount || getMaxScrollY() - getActualScrollY() < 0) {
      firstRow = 0;
      scrollY = Integer.MAX_VALUE;
    }
    if (firstColumn >= columnCount || getMaxScrollX() - getActualScrollX() < 0) {
      firstColumn = 0;
      scrollX = Integer.MAX_VALUE;
    }

    setMeasuredDimension(w, h);
  }
  @Override
  protected void onDraw(Canvas canvas) {
    float ringWidth = textHeight + 4;
    int height = getMeasuredHeight();
    int width = getMeasuredWidth();

    int px = width / 2;
    int py = height / 2;
    Point center = new Point(px, py);

    int radius = Math.min(px, py) - 2;

    RectF boundingBox =
        new RectF(center.x - radius, center.y - radius, center.x + radius, center.y + radius);

    RectF innerBoundingBox =
        new RectF(
            center.x - radius + ringWidth,
            center.y - radius + ringWidth,
            center.x + radius - ringWidth,
            center.y + radius - ringWidth);

    float innerRadius = innerBoundingBox.height() / 2;
    RadialGradient borderGradient =
        new RadialGradient(
            px, py, radius, borderGradientColors, borderGradientPositions, TileMode.CLAMP);

    Paint pgb = new Paint();
    pgb.setShader(borderGradient);

    Path outerRingPath = new Path();
    outerRingPath.addOval(boundingBox, Direction.CW);

    canvas.drawPath(outerRingPath, pgb);
    LinearGradient skyShader =
        new LinearGradient(
            center.x,
            innerBoundingBox.top,
            center.x,
            innerBoundingBox.bottom,
            skyHorizonColorFrom,
            skyHorizonColorTo,
            TileMode.CLAMP);

    Paint skyPaint = new Paint();
    skyPaint.setShader(skyShader);

    LinearGradient groundShader =
        new LinearGradient(
            center.x,
            innerBoundingBox.top,
            center.x,
            innerBoundingBox.bottom,
            groundHorizonColorFrom,
            groundHorizonColorTo,
            TileMode.CLAMP);

    Paint groundPaint = new Paint();
    groundPaint.setShader(groundShader);
    float tiltDegree = pitch;
    while (tiltDegree > 90 || tiltDegree < -90) {
      if (tiltDegree > 90) tiltDegree = -90 + (tiltDegree - 90);
      if (tiltDegree < -90) tiltDegree = 90 - (tiltDegree + 90);
    }

    float rollDegree = roll;
    while (rollDegree > 180 || rollDegree < -180) {
      if (rollDegree > 180) rollDegree = -180 + (rollDegree - 180);
      if (rollDegree < -180) rollDegree = 180 - (rollDegree + 180);
    }
    Path skyPath = new Path();
    skyPath.addArc(innerBoundingBox, -rollDegree, (180 + (2 * rollDegree)));
    canvas.rotate(-tiltDegree, px, py);
    canvas.drawOval(innerBoundingBox, groundPaint);
    canvas.drawPath(skyPath, skyPaint);
    canvas.drawPath(skyPath, markerPaint);
    int markWidth = radius / 3;
    int startX = center.x - markWidth;
    int endX = center.x + markWidth;

    Log.d("PAARV ", "Roll " + String.valueOf(rollDegree));
    Log.d("PAARV ", "Pitch " + String.valueOf(tiltDegree));

    double h = innerRadius * Math.cos(Math.toRadians(90 - tiltDegree));
    double justTiltX = center.x - h;

    float pxPerDegree = (innerBoundingBox.height() / 2) / 45f;
    for (int i = 90; i >= -90; i -= 10) {
      double ypos = justTiltX + i * pxPerDegree;

      if ((ypos < (innerBoundingBox.top + textHeight))
          || (ypos > innerBoundingBox.bottom - textHeight)) continue;

      canvas.drawLine(startX, (float) ypos, endX, (float) ypos, markerPaint);
      int displayPos = (int) (tiltDegree - i);
      String displayString = String.valueOf(displayPos);
      float stringSizeWidth = textPaint.measureText(displayString);
      canvas.drawText(
          displayString, (int) (center.x - stringSizeWidth / 2), (int) (ypos) + 1, textPaint);
    }
    markerPaint.setStrokeWidth(2);
    canvas.drawLine(
        center.x - radius / 2,
        (float) justTiltX,
        center.x + radius / 2,
        (float) justTiltX,
        markerPaint);
    markerPaint.setStrokeWidth(1);

    Path rollArrow = new Path();
    rollArrow.moveTo(center.x - 3, (int) innerBoundingBox.top + 14);
    rollArrow.lineTo(center.x, (int) innerBoundingBox.top + 10);
    rollArrow.moveTo(center.x + 3, innerBoundingBox.top + 14);
    rollArrow.lineTo(center.x, innerBoundingBox.top + 10);
    canvas.drawPath(rollArrow, markerPaint);
    String rollText = String.valueOf(rollDegree);
    double rollTextWidth = textPaint.measureText(rollText);
    canvas.drawText(
        rollText,
        (float) (center.x - rollTextWidth / 2),
        innerBoundingBox.top + textHeight + 2,
        textPaint);
    canvas.restore();

    canvas.save();
    canvas.rotate(180, center.x, center.y);
    for (int i = -180; i < 180; i += 10) {
      if (i % 30 == 0) {
        String rollString = String.valueOf(i * -1);
        float rollStringWidth = textPaint.measureText(rollString);
        PointF rollStringCenter =
            new PointF(center.x - rollStringWidth / 2, innerBoundingBox.top + 1 + textHeight);
        canvas.drawText(rollString, rollStringCenter.x, rollStringCenter.y, textPaint);
      } else {
        canvas.drawLine(
            center.x,
            (int) innerBoundingBox.top,
            center.x,
            (int) innerBoundingBox.top + 5,
            markerPaint);
      }

      canvas.rotate(10, center.x, center.y);
    }
    canvas.restore();
    canvas.save();
    canvas.rotate(-1 * (bearing), px, py);

    double increment = 22.5;

    for (double i = 0; i < 360; i += increment) {
      CompassDirection cd = CompassDirection.values()[(int) (i / 22.5)];
      String headString = cd.toString();

      float headStringWidth = textPaint.measureText(headString);
      PointF headStringCenter =
          new PointF(center.x - headStringWidth / 2, boundingBox.top + 1 + textHeight);

      if (i % increment == 0)
        canvas.drawText(headString, headStringCenter.x, headStringCenter.y, textPaint);
      else
        canvas.drawLine(
            center.x, (int) boundingBox.top, center.x, (int) boundingBox.top + 3, markerPaint);

      canvas.rotate((int) increment, center.x, center.y);
    }
    canvas.restore();
    RadialGradient glassShader =
        new RadialGradient(
            px, py, (int) innerRadius, glassGradientColors, glassGradientPositions, TileMode.CLAMP);
    Paint glassPaint = new Paint();
    glassPaint.setShader(glassShader);

    canvas.drawOval(innerBoundingBox, glassPaint);
    canvas.drawOval(boundingBox, circlePaint);

    circlePaint.setStrokeWidth(2);
    canvas.drawOval(innerBoundingBox, circlePaint);

    canvas.restore();
  }