package com.tencent.tws.assistant.widget; import com.tencent.tws.sharelib.R; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; import android.graphics.PixelFormat; import android.graphics.Paint.Align; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Handler; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewParent; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; public class SideBar extends View { public class Entrie { public Entrie(CharSequence ch) { letter = ch; } CharSequence letter = " "; boolean exist = true; } private OnTouchingLetterChangedListener onTouchingLetterChangedListener; private final String[] DEFAULT_ENTRIES_CSP = { "★", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#" }; private final String[] DEFAULT_ENTRIES = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#" }; public static final String TAG = "SideBar"; private int mCurrentLetterIndex = 0; private Paint mNormalPaint = new Paint(); private Paint mSelectedPaint = new Paint(); private Paint mNonExistPaint = new Paint(); private int mNormalColor; private int mSelectedColor; private int mNonExistColor; private int mItemHeight = 1; private int mSpace = 0; private Drawable mChildCheckedBackground = null; private Entrie[] mSideBarEntries = null; private Rect mChildRect = new Rect(); private boolean mShowing; private boolean mReady; private AddWindow mAddWindow = new AddWindow(); private HideWindow mHideWindow = new HideWindow(); private RemoveWindow mRemoveWindow = new RemoveWindow(); private Handler mHandler = new Handler(); private WindowManager mWindowManager; private WindowManager.LayoutParams mLayoutParams; private boolean mHasDialog = true; private boolean mUpdateEnable = true; private boolean mIsCustomChanger = false; private int mRealCurrentIndex = -1; private boolean mCurrentIndexNotExist = false; // hubble private SidebarHubbleView mHubbleView = null; private int mDelayTime = 200; private final int fSideBarHubbleViewWith = (int) getResources().getDimension(R.dimen.sidbar_hubble_w); private final int fSideBarHubbleViewHeigh = (int) getResources().getDimension(R.dimen.sidbar_hubble_h); private final float SIDEBAR_LETTER_SIZE = getResources().getDimension(R.dimen.sidbar_letter_size); private final int SIDEBAR_PADDING_RIGHT = (int) getResources().getDimension(R.dimen.sidbar_padding_right); private final int SIDEBAR_PADDING_TOP = (int) getResources().getDimension(R.dimen.sidbar_padding_top); private final int SIDEBAR_PADDING_BOTTOM = (int) getResources().getDimension(R.dimen.sidbar_padding_bottom); // property private int mScreenH; // in Screen private int mSideBarGlobalVisibleTop = 0; private FontMetricsInt mFontMetricsInt = null; // value private int mSidebarPaddingTop = 0; private int mSidebarPaddingBottom = 0; private boolean mIsCSP = false; public SideBar(Context context) { this(context, null); } public SideBar(Context context, AttributeSet attrs) { this(context, attrs, R.attr.SideBarStyle); } @SuppressWarnings("deprecation") public SideBar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mLayoutParams = new WindowManager.LayoutParams(fSideBarHubbleViewWith, fSideBarHubbleViewHeigh, WindowManager.LayoutParams.TYPE_APPLICATION, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT); mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; mLayoutParams.x = mWindowManager.getDefaultDisplay().getWidth() - fSideBarHubbleViewWith; TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.SideBar, defStyle, 0); mChildCheckedBackground = attributesArray.getDrawable(R.styleable.SideBar_childSelectedBackground); mNormalColor = attributesArray.getColor(R.styleable.SideBar_childNormalTextColor, 0xcc000000); mSelectedColor = attributesArray.getColor(R.styleable.SideBar_childSelectedTextColor, 0xff22b2b6); mNonExistColor = attributesArray.getColor(R.styleable.SideBar_childNonExistTextColor, 0x4c000000); int hubbleNormalBgColor = attributesArray.getColor(R.styleable.SideBar_hubbleNormalBackground, 0xFF000000); int hubbleNonExistBgColor = attributesArray.getColor(R.styleable.SideBar_hubbleNonExistBackground, 0xFFe5e5e5); int hubbleNormalTextColor = attributesArray.getColor(R.styleable.SideBar_hubbleNormalTextColor, 0xFFFFFFFF); int hubbleNonExistTextColor = attributesArray.getColor(R.styleable.SideBar_hubbleNonExistTextColor, 0xFFFFFFFF); CharSequence[] sideBarEntries = attributesArray.getTextArray(R.styleable.SideBar_android_entries); initSideBarEntries(); if (sideBarEntries != null) { updateEntriesPropWithContentArray(sideBarEntries); } attributesArray.recycle(); mHubbleView = new SidebarHubbleView(context); mHubbleView.setNormalTextColor(hubbleNormalTextColor); mHubbleView.setNormalBgColor(hubbleNormalBgColor); mHubbleView.setNonExistTextColor(hubbleNonExistTextColor); mHubbleView.setNonExistBgColor(hubbleNonExistBgColor); mNormalPaint.setAntiAlias(true); mNormalPaint.setTextAlign(Align.CENTER); mNormalPaint.setColor(mNormalColor); mNormalPaint.setTextSize(SIDEBAR_LETTER_SIZE); mSelectedPaint.setAntiAlias(true); mSelectedPaint.setTextAlign(Align.CENTER); mSelectedPaint.setColor(mSelectedColor); mSelectedPaint.setTextSize(SIDEBAR_LETTER_SIZE); mNonExistPaint.setAntiAlias(true); mNonExistPaint.setTextAlign(Align.CENTER); mNonExistPaint.setColor(mNonExistColor); mNonExistPaint.setTextSize(SIDEBAR_LETTER_SIZE); Log.d(TAG, "SIDEBAR_LETTER_SIZE=" + SIDEBAR_LETTER_SIZE + " SIDEBAR_PADDING_RIGHT=" + SIDEBAR_PADDING_RIGHT + " SIDEBAR_PADDING_TOP=" + SIDEBAR_PADDING_TOP + " SIDEBAR_PADDING_BOTTOM=" + SIDEBAR_PADDING_BOTTOM); } public void setIsCSP(boolean isCSP) { if (mIsCSP != isCSP) { mIsCSP = isCSP; initSideBarEntries(); } } public void setNormalColor(int color) { if (mNormalColor != color) { mNormalColor = color; mNormalPaint.setColor(mNormalColor); } } public void setSelectedColor(int color) { if (mSelectedColor != color) { mSelectedColor = color; mSelectedPaint.setColor(mSelectedColor); } } public void setNonExistColor(int color) { if (mNonExistColor != color) { mNonExistColor = color; mNonExistPaint.setColor(mNonExistColor); } } public void setHubbleNormalTextColor(int color) { if (mHubbleView != null) mHubbleView.setNormalTextColor(color); } public void setHubbleNonExistTextColor(int color) { if (mHubbleView != null) mHubbleView.setNonExistTextColor(color); } public void setHubbleNormalBgColor(int color) { if (mHubbleView != null) mHubbleView.setNormalBgColor(color); } public void setHubbleNonExistBgColor(int color) { if (mHubbleView != null) mHubbleView.setNonExistBgColor(color); } private void initSideBarEntries() { mSideBarEntries = null; if (mIsCSP) { mSideBarEntries = new Entrie[DEFAULT_ENTRIES_CSP.length]; for (int index = 0; index < DEFAULT_ENTRIES_CSP.length; index++) { mSideBarEntries[index] = new Entrie(DEFAULT_ENTRIES_CSP[index]); } } else { mSideBarEntries = new Entrie[DEFAULT_ENTRIES.length]; for (int index = 0; index < DEFAULT_ENTRIES.length; index++) { mSideBarEntries[index] = new Entrie(DEFAULT_ENTRIES[index]); } } } public void updateEntriesPropWithContentArray(CharSequence[] contentLetters) { changeEntriesToNotExist(); char ch; for (int index = 0; index < contentLetters.length; index++) { if (contentLetters[index] != null && 0 < contentLetters[index].length()) { ch = Character.toUpperCase(contentLetters[index].charAt(0)); updateEntriesPropWithCharValue(ch); } } } public void updateEntriesPropForAddOneContent(CharSequence content) { if (content != null && 0 < content.length()) { updateEntriesPropWithCharValue(content.charAt(0)); } } private final char CHAR_A = 'A'; private final char CHAR_Z = 'Z'; private void updateEntriesPropWithCharValue(char ch) { if (ch < CHAR_A) { updateExistEntries(mIsCSP ? DEFAULT_ENTRIES_CSP.length - 1 : DEFAULT_ENTRIES.length - 1); } else if (CHAR_Z < ch) { updateExistEntries((mIsCSP && ch == DEFAULT_ENTRIES_CSP[0].charAt(0)) ? 0 : DEFAULT_ENTRIES.length - 1); } else { updateExistEntries(mIsCSP ? 1 + ch - CHAR_A : ch - CHAR_A); } } private void updateExistEntries(int index) { if (0 <= index && index < mSideBarEntries.length) { mSideBarEntries[index].exist = true; } } private void changeEntriesToNotExist() { for (int index = 0; index < mSideBarEntries.length; index++) mSideBarEntries[index].exist = false; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mReady = true; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); hideHubbleWithAnimation(false); mReady = false; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final int width = getWidth(); if (mFontMetricsInt == null) mFontMetricsInt = mNormalPaint.getFontMetricsInt(); // mChildRect.set(0, 0, width, getHeight() - mSidebarPaddingTop - mSidebarPaddingBottom); for (int i = 0; i < mSideBarEntries.length; i++) { float baseLine = mSidebarPaddingTop + mSpace + i * mItemHeight + (mItemHeight - mFontMetricsInt.bottom - mFontMetricsInt.top) / 2; if (i == mCurrentLetterIndex && mUpdateEnable && mSideBarEntries[i].exist) { if (mChildCheckedBackground != null) { mChildRect.set((width - mItemHeight) / 2, (int) (mSpace + mItemHeight * i), (width + mItemHeight) / 2, (int) (mSpace + mItemHeight * (i + 1))); mChildCheckedBackground.setBounds(mChildRect); mChildCheckedBackground.draw(canvas); } canvas.drawText(mSideBarEntries[i].letter + "", width / 2, baseLine, mSelectedPaint); } else { if (mSideBarEntries[i].exist) canvas.drawText(mSideBarEntries[i].letter + "", width / 2, baseLine, mNormalPaint); else canvas.drawText(mSideBarEntries[i].letter + "", width / 2, baseLine, mNonExistPaint); } } } @Override public boolean dispatchTouchEvent(MotionEvent event) { super.dispatchTouchEvent(event); hideSoftKeyboard(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: setPressed(true); handleTouchIndex(event); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: setPressed(false); hideWindow(); if (onTouchingLetterChangedListener != null) { onTouchingLetterChangedListener.onTouchUp(); } break; default: break; } return true; } private void handleTouchIndex(MotionEvent event) { int index = Math.max(0, Math.min((int) ((event.getY() - mSpace - mSidebarPaddingTop) / mItemHeight), mSideBarEntries.length - 1)); String letterString = mSideBarEntries[index].letter + ""; if (mFontMetricsInt == null) mFontMetricsInt = mNormalPaint.getFontMetricsInt(); float baseLine = mSidebarPaddingTop + mSpace + index * mItemHeight + (mItemHeight - mFontMetricsInt.bottom - mFontMetricsInt.top) / 2; mLayoutParams.y = mSideBarGlobalVisibleTop + (int) baseLine - fSideBarHubbleViewHeigh / 2 - (mFontMetricsInt.bottom - mFontMetricsInt.top) / 3; if (mIsCustomChanger) { if (onTouchingLetterChangedListener != null) { onTouchingLetterChangedListener.onTouchingLetterChanged(index); onTouchingLetterChangedListener.onTouchingLetterChanged(letterString); } if (mCurrentIndexNotExist) { mCurrentIndexNotExist = false; } else { mRealCurrentIndex = index; } if (mReady && mHasDialog) { updateHubble(mLayoutParams, index); } } else { if (mReady && mHasDialog) { updateHubble(mLayoutParams, index); } if (onTouchingLetterChangedListener != null) { onTouchingLetterChangedListener.onTouchingLetterChanged(index); onTouchingLetterChangedListener.onTouchingLetterChanged(letterString); } } mCurrentLetterIndex = index; invalidate(); } private void hideWindow() { if (mHandler != null && mHubbleView != null) { mHandler.removeCallbacks(mHideWindow); int restInTime = (int) (mHubbleView.theRestOfInAnim() * mHubbleView.getHubbleInPerFrame() * SidebarHubbleView.IN_FRAME_COUNT); mHandler.postDelayed(mHideWindow, restInTime + mDelayTime); } } @Override public void layout(int l, int t, int r, int b) { super.layout(l, t, r, b); getDisplayInfo(); } @SuppressWarnings("deprecation") private void getDisplayInfo() { mScreenH = mWindowManager.getDefaultDisplay().getHeight(); Rect outRect = new Rect(); getGlobalVisibleRect(outRect); mSidebarPaddingTop = Math.max(SIDEBAR_PADDING_TOP - outRect.top, 0); mSideBarGlobalVisibleTop = outRect.top; mSidebarPaddingBottom = Math.max(SIDEBAR_PADDING_BOTTOM + outRect.bottom - mScreenH, 0); int height = getHeight() - mSidebarPaddingTop - mSidebarPaddingBottom; int size = mSideBarEntries.length; mItemHeight = Math.max(height / size, 1); mSpace = (height - mItemHeight * size) / 2; } private void updateHubble(WindowManager.LayoutParams layoutParams, int index) { if (mHubbleView != null) { String letterString = mSideBarEntries[index].letter + ""; mHubbleView.setText(letterString); mHubbleView.setIndexIsExsit(mSideBarEntries[index].exist); } clearCallbacks(); if (mHandler != null) { mHubbleView.stopOut(); mHandler.post(mAddWindow); } } private void hideSoftKeyboard() { // Hide soft keyboard, if visible InputMethodManager inputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); } public void setOnTouchingLetterChangedListener(OnTouchingLetterChangedListener onTouchingLetterChangedListener) { this.onTouchingLetterChangedListener = onTouchingLetterChangedListener; } public void updateCurrentIndex(int index) { mCurrentLetterIndex = index; invalidate(); } public void updateCurrentIndex(String indexString) { if (TextUtils.isEmpty(indexString)) { return; } for (int i = 0; i < mSideBarEntries.length; i++) { if (TextUtils.equals(mSideBarEntries[i].letter, indexString)) { mCurrentLetterIndex = i; break; } } invalidate(); } public void setUpdateEnable(boolean enable) { mUpdateEnable = enable; } public void setSideBarEntries(CharSequence[] sideBarEntries) { updateEntriesPropWithContentArray(sideBarEntries); // mSideBarEntries = sideBarEntries; invalidate(); } public CharSequence[] getSideBarEntries() { if (mIsCSP) return DEFAULT_ENTRIES_CSP; else return DEFAULT_ENTRIES; } public void setHasDialog(boolean hasDialog) { mHasDialog = hasDialog; if (!hasDialog) { hideHubbleWithAnimation(false); } } public boolean getHasDialog() { return mHasDialog; } private final class RemoveWindow implements Runnable { public void run() { if (mHubbleView != null) { mHubbleView.setVisibility(View.INVISIBLE); try { if (mWindowManager != null) { mShowing = false; mWindowManager.removeView(mHubbleView); } } catch (Exception e) { e.printStackTrace(); } } } } private final class HideWindow implements Runnable { public void run() { hideHubbleWithAnimation(true); } } private final class AddWindow implements Runnable { public void run() { if (mWindowManager != null && mHubbleView != null && mLayoutParams != null) { mShowing = mReady = true; ViewParent viewParent = mHubbleView.getParent(); mHubbleView.setVisibility(View.VISIBLE); if (viewParent != null) { mWindowManager.updateViewLayout(mHubbleView, mLayoutParams); } else { mWindowManager.addView(mHubbleView, mLayoutParams); mHubbleView.showHubbleWithAnimation(); } } } } public int getDialogDelayTime() { return mDelayTime; } public void setDialogDelayTime(int delayTime) { mDelayTime = delayTime; } private void hideHubbleWithAnimation(boolean hasAnimation) { clearCallbacks(); if (!hasAnimation || !mShowing) { if (mHubbleView != null) mHubbleView.stopOut(); if (mHandler != null) mHandler.post(mRemoveWindow); return; } if (hasAnimation) { if (mHubbleView != null && mHandler != null){ mHubbleView.hidHubbleWithAnimation(); mHandler.postDelayed(mRemoveWindow, mHubbleView.getHubbleOutPerFrame() * SidebarHubbleView.OUT_FRAME_COUNT); } } else { if (mHubbleView != null) mHubbleView.stopOut(); if (mHandler != null) { mHandler.post(mRemoveWindow); } } } private void clearCallbacks() { if (mHandler != null) { mHandler.removeCallbacks(mHideWindow); mHandler.removeCallbacks(mRemoveWindow); mHandler.removeCallbacks(mAddWindow); } } // Compatible with the old V public void hideDialog(boolean hasAnimation) { hideHubbleWithAnimation(hasAnimation); } public interface OnTouchingLetterChangedListener { public void onTouchUp(); public void onTouchingLetterChanged(String touchIndexString); public void onTouchingLetterChanged(int letterIndex); } public void setCustomChanger(boolean isCustom) { mIsCustomChanger = isCustom; } public void setRealCurrentIndex(int realCurrentIndex) { mCurrentIndexNotExist = true; mRealCurrentIndex = realCurrentIndex; } }