編輯:關於Android編程
項目需要,今天學習了一下索引
涉及到的技術:
繪制右側的索引條
點擊某個字母,定位到ListView控件的指定位置
布局文件:
自定義索引條:
package com.example.suoyin; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.view.MotionEvent; import android.widget.Adapter; import android.widget.ListView; import android.widget.SectionIndexer; /** * 右側的索引條 * * @author by 佚名 * */ public class IndexScroller { private float mIndexbarWidth; // 索引條寬度 private float mIndexbarMargin; // 索引條外邊距 private float mPreviewPadding; // private float mDensity; // 密度 private float mScaledDensity; // 縮放密度 private float mAlphaRate; // 透明度 private int mState = STATE_HIDDEN; // 狀態 private int mListViewWidth; // ListView寬度 private int mListViewHeight; // ListView高度 private int mCurrentSection = -1; // 當前部分 private boolean mIsIndexing = false; // 是否正在索引 private ListView mListView = null; private SectionIndexer mIndexer = null; private String[] mSections = null; private RectF mIndexbarRect; // 4種狀態(已隱藏、正在顯示、已顯示、正在隱藏) private static final int STATE_HIDDEN = 0; private static final int STATE_SHOWING = 1; private static final int STATE_SHOWN = 2; private static final int STATE_HIDING = 3; public IndexScroller(Context context, ListView lv) { mDensity = context.getResources().getDisplayMetrics().density; mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity; mListView = lv; setAdapter(mListView.getAdapter()); mIndexbarWidth = 20 * mDensity; // 索引條寬度 mIndexbarMargin = 10 * mDensity;// 索引條間距 mPreviewPadding = 5 * mDensity; // 內邊距 } public void draw(Canvas canvas) { if (mState == STATE_HIDDEN) return; // mAlphaRate determines the rate of opacity Paint indexbarPaint = new Paint(); indexbarPaint.setColor(Color.BLACK); indexbarPaint.setAlpha((int) (64 * mAlphaRate)); indexbarPaint.setAntiAlias(true); // 畫右側字母索引的圓矩形 canvas.drawRoundRect(mIndexbarRect, 5 * mDensity, 5 * mDensity, indexbarPaint); if (mSections != null && mSections.length > 0) { // Preview is shown when mCurrentSection is set if (mCurrentSection >= 0) { Paint previewPaint = new Paint(); // 用來繪畫所以條背景的畫筆 previewPaint.setColor(Color.BLACK);// 設置畫筆顏色為黑色 previewPaint.setAlpha(96); // 設置透明度 previewPaint.setAntiAlias(true);// 設置抗鋸齒 previewPaint.setShadowLayer(3, 0, 0, Color.argb(64, 0, 0, 0)); // 設置陰影層 Paint previewTextPaint = new Paint(); // 用來繪畫索引字母的畫筆 previewTextPaint.setColor(Color.WHITE); // 設置畫筆為白色 previewTextPaint.setAntiAlias(true); // 設置抗鋸齒 previewTextPaint.setTextSize(50 * mScaledDensity); // 設置字體大小 // 文本的寬度 float previewTextWidth = previewTextPaint .measureText(mSections[mCurrentSection]); float previewSize = 2 * mPreviewPadding + previewTextPaint.descent() - previewTextPaint.ascent(); RectF previewRect = new RectF( (mListViewWidth - previewSize) / 2, (mListViewHeight - previewSize) / 2, (mListViewWidth - previewSize) / 2 + previewSize, (mListViewHeight - previewSize) / 2 + previewSize); // 中間索引的那個框 canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint); // 繪畫索引字母 canvas.drawText( mSections[mCurrentSection], previewRect.left + (previewSize - previewTextWidth) / 2 - 1, previewRect.top + mPreviewPadding - previewTextPaint.ascent() + 1, previewTextPaint); } // 繪畫右側索引條的字母 Paint indexPaint = new Paint(); indexPaint.setColor(Color.WHITE); indexPaint.setAlpha((int) (255 * mAlphaRate)); indexPaint.setAntiAlias(true); indexPaint.setTextSize(12 * mScaledDensity); float sectionHeight = (mIndexbarRect.height() - 2 * mIndexbarMargin) / mSections.length; float paddingTop = (sectionHeight - (indexPaint.descent() - indexPaint .ascent())) / 2; for (int i = 0; i < mSections.length; i++) { float paddingLeft = (mIndexbarWidth - indexPaint .measureText(mSections[i])) / 2; canvas.drawText(mSections[i], mIndexbarRect.left + paddingLeft, mIndexbarRect.top + mIndexbarMargin + sectionHeight * i + paddingTop - indexPaint.ascent(), indexPaint); } } } public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // 按下,開始索引 // If down event occurs inside index bar region, start indexing if (mState != STATE_HIDDEN && contains(ev.getX(), ev.getY())) { setState(STATE_SHOWN); // It demonstrates that the motion event started from index bar mIsIndexing = true; // Determine which section the point is in, and move the list to // that section mCurrentSection = getSectionByPoint(ev.getY()); mListView.setSelection(mIndexer .getPositionForSection(mCurrentSection)); return true; } break; case MotionEvent.ACTION_MOVE: // 移動 if (mIsIndexing) { // If this event moves inside index bar if (contains(ev.getX(), ev.getY())) { // Determine which section the point is in, and move the // list to that section mCurrentSection = getSectionByPoint(ev.getY()); mListView.setSelection(mIndexer .getPositionForSection(mCurrentSection)); } return true; } break; case MotionEvent.ACTION_UP: // 抬起 if (mIsIndexing) { mIsIndexing = false; mCurrentSection = -1; } if (mState == STATE_SHOWN) setState(STATE_HIDING); break; } return false; } public void onSizeChanged(int w, int h, int oldw, int oldh) { mListViewWidth = w; mListViewHeight = h; mIndexbarRect = new RectF(w - mIndexbarMargin - mIndexbarWidth, mIndexbarMargin, w - mIndexbarMargin, h - mIndexbarMargin); } // 顯示 public void show() { if (mState == STATE_HIDDEN) setState(STATE_SHOWING); else if (mState == STATE_HIDING) setState(STATE_HIDING); } // 隱藏 public void hide() { if (mState == STATE_SHOWN) setState(STATE_HIDING); } public void setAdapter(Adapter adapter) { if (adapter instanceof SectionIndexer) { mIndexer = (SectionIndexer) adapter; mSections = (String[]) mIndexer.getSections(); } } // 設置狀態 private void setState(int state) { if (state < STATE_HIDDEN || state > STATE_HIDING) return; mState = state; switch (mState) { case STATE_HIDDEN: // Cancel any fade effect // 取消漸退的效果 mHandler.removeMessages(0); break; case STATE_SHOWING: // Start to fade in // 開始漸進效果 mAlphaRate = 0; fade(0); break; case STATE_SHOWN: // Cancel any fade effect // 取消漸退的效果 mHandler.removeMessages(0); break; case STATE_HIDING: // Start to fade out after three seconds // 隱藏3秒鐘 mAlphaRate = 1; fade(3000); break; } } private boolean contains(float x, float y) { // Determine if the point is in index bar region, which includes the // right margin of the bar return (x >= mIndexbarRect.left && y >= mIndexbarRect.top && y <= mIndexbarRect.top + mIndexbarRect.height()); } private int getSectionByPoint(float y) { if (mSections == null || mSections.length == 0) return 0; if (y < mIndexbarRect.top + mIndexbarMargin) return 0; if (y >= mIndexbarRect.top + mIndexbarRect.height() - mIndexbarMargin) return mSections.length - 1; return (int) ((y - mIndexbarRect.top - mIndexbarMargin) / ((mIndexbarRect .height() - 2 * mIndexbarMargin) / mSections.length)); } private void fade(long delay) { mHandler.removeMessages(0); mHandler.sendEmptyMessageAtTime(0, SystemClock.uptimeMillis() + delay); } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (mState) { case STATE_SHOWING: // Fade in effect // 淡進效果 mAlphaRate += (1 - mAlphaRate) * 0.2; if (mAlphaRate > 0.9) { mAlphaRate = 1; setState(STATE_SHOWN); } mListView.invalidate(); fade(10); break; case STATE_SHOWN: // If no action, hide automatically setState(STATE_HIDING); break; case STATE_HIDING: // Fade out effect // 淡出效果 mAlphaRate -= mAlphaRate * 0.2; if (mAlphaRate < 0.1) { mAlphaRate = 0; setState(STATE_HIDDEN); } mListView.invalidate(); fade(10); break; } } }; }
package com.example.suoyin; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.widget.ListAdapter; import android.widget.ListView; /** * 自定義索引列表 * * @author by 佚名 * */ public class IndexableListView extends ListView { private boolean mIsFastScrollEnabled = false; private IndexScroller mScroller = null;//繪制索引條 private GestureDetector mGestureDetector = null;//檢查上下滑動的手勢 public IndexableListView(Context context) { super(context); } public IndexableListView(Context context, AttributeSet attrs) { super(context, attrs); } public IndexableListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean isFastScrollEnabled() { return mIsFastScrollEnabled; } @Override public void setFastScrollEnabled(boolean enabled) { mIsFastScrollEnabled = enabled; if (mIsFastScrollEnabled) { if (mScroller == null) mScroller = new IndexScroller(getContext(), this); } else { if (mScroller != null) { mScroller.hide(); mScroller = null; } } } @Override public void draw(Canvas canvas) { super.draw(canvas); // Overlay index bar if (mScroller != null) mScroller.draw(canvas); } @Override public boolean onTouchEvent(MotionEvent ev) { // Intercept ListView's touch event if (mScroller != null && mScroller.onTouchEvent(ev)) return true; if (mGestureDetector == null) { // 創建一個GestureDetector(手勢探測器) mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // If fling happens, index bar shows // 顯示索引條 mScroller.show(); return super.onFling(e1, e2, velocityX, velocityY); } }); } mGestureDetector.onTouchEvent(ev); return super.onTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); if (mScroller != null) mScroller.setAdapter(adapter); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mScroller != null) mScroller.onSizeChanged(w, h, oldw, oldh); } }
字符串匹配類:將索引條字母與索引列表項進行匹配判斷
package com.example.suoyin; public class StringMatcher { // private final static char KOREAN_UNICODE_START = '?'; // 韓文字符編碼開始? private final static char KOREAN_UNICODE_END = '?'; // 韓文字符編碼結束? private final static char KOREAN_UNIT = '?' - '?'; // 不知道是啥? // 韓文的一些字符初始化 private final static char[] KOREAN_INITIAL = { '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?' }; /** * 字符匹配 * @param value 需要keyword匹配的字符串 list中的文本 * @param keyword #ABCDEFGHIJKLMNOPQRSTUVWXYZ中的一個 * @return 只要value中包含keyword就返回真 */ public static boolean match(String value, String keyword) { if (value == null || keyword == null) return false; if (keyword.length() > value.length()) return false;//在一個小的字符串中查找一個大的字符串肯定找不到 int i = 0, j = 0; do { // 如果是韓文字符並且在韓文初始數組裡面 if (isKorean(value.charAt(i)) && isInitialSound(keyword.charAt(j))) { if (keyword.charAt(j) == getInitialSound(value.charAt(i))) { i++; j++; } else if (j > 0) break; else i++; } else { // 逐個字符匹配 if (keyword.charAt(j) == value.charAt(i)) { i++; j++; } else if (j > 0) break; else i++; } } while (i < value.length() && j < keyword.length()); // 如果最後j等於keyword的長度說明匹配成功 return (j == keyword.length()) ? true : false; } // 判斷字符是否在韓文字符編碼范圍內 private static boolean isKorean(char c) { if (c >= KOREAN_UNICODE_START && c <= KOREAN_UNICODE_END) return true; return false; } // 判斷是否在韓文字符裡面 private static boolean isInitialSound(char c) { for (char i : KOREAN_INITIAL) { if (c == i) return true; } return false; } // 獲得韓文初始化字符數組裡面的一個字符 private static char getInitialSound(char c) { return KOREAN_INITIAL[(c - KOREAN_UNICODE_START) / KOREAN_UNIT]; } }
package com.example.suoyin; import java.util.ArrayList; import java.util.Collections; import java.util.List; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.widget.ArrayAdapter; import android.widget.SectionIndexer; public class IndexableListViewActivity extends Activity { private ArrayListmItems; private IndexableListView mListView; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化一些數據 mItems = new ArrayList (); mItems.add("Diary of a Wimpy Kid 6: Cabin Fever"); mItems.add("Steve Jobs"); mItems.add("Inheritance (The Inheritance Cycle)"); mItems.add("11/22/63: A Novel"); mItems.add("The Hunger Games"); mItems.add("The LEGO Ideas Book"); mItems.add("Explosive Eighteen: A Stephanie Plum Novel"); mItems.add("Catching Fire (The Second Book of the Hunger Games)"); mItems.add("Elder Scrolls V: Skyrim: Prima Official Game Guide"); mItems.add("Death Comes to Pemberley"); mItems.add("Diary of a Wimpy Kid 6: Cabin Fever"); mItems.add("Steve Jobs"); mItems.add("Inheritance (The Inheritance Cycle)"); mItems.add("11/22/63: A Novel"); mItems.add("The Hunger Games"); mItems.add("The LEGO Ideas Book"); mItems.add("Explosive Eighteen: A Stephanie Plum Novel"); mItems.add("Catching Fire (The Second Book of the Hunger Games)"); mItems.add("Elder Scrolls V: Skyrim: Prima Official Game Guide"); mItems.add("做作"); mItems.add("1"); mItems.add("2"); mItems.add("wokao"); Collections.sort(mItems); // 排序 ContentAdapter adapter = new ContentAdapter(this,android.R.layout.simple_list_item_1,mItems); mListView = (IndexableListView) findViewById(R.id.listview); mListView.setAdapter(adapter); mListView.setFastScrollEnabled(true); // 設置快速滑動 } private class ContentAdapter extends ArrayAdapter implements SectionIndexer { private String mSections = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ"; public ContentAdapter(Context context, int textViewResourceId, List objects) { super(context, textViewResourceId, objects); } @Override public int getPositionForSection(int sectionIndex) { /*根據右邊索引adbcd...獲取左邊listView的位置*/ // If there is no item for current section, previous section will be // selected // 如果當前部分沒有對應item,則之前的部分將被選擇 (比如用戶點擊索引Y,左邊list中沒有y開頭的,則會選擇y之前的x,x也沒有就找w,一直往前查,直到遇到第一個有對應item的,否則不進行定位) for (int i = sectionIndex; i >= 0; i--) { for (int j = 0; j < getCount(); j++) { System.out.println(getCount()); if (i == 0) { // # // For numeric section 數字 for (int k = 0; k <= 9; k++) {// 1...9 // 字符串第一個字符與1~9之間的數字進行匹配 if (StringMatcher.match( String.valueOf(getItem(j).charAt(0)), String.valueOf(k))) return j; } } else { // A~Z if (StringMatcher.match( String.valueOf(getItem(j).charAt(0)), String.valueOf(mSections.charAt(i)))) return j; } } } return 0; } @Override public int getSectionForPosition(int position) { return 0; } @Override public Object[] getSections() { String[] sections = new String[mSections.length()]; for (int i = 0; i < mSections.length(); i++) sections[i] = String.valueOf(mSections.charAt(i)); return sections; } } }
在給大家講解了paint的幾個方法之後,我覺得有必要插一篇有關Canvas畫布的知識,在開始paint之前,我們講解了canvas繪圖的幾篇文章和cavas的save()
這篇文章主要介紹一下如何實現View的3D旋轉效果,實現的主要原理就是圍繞Y軸旋轉,同時在Z軸方面上有一個深入的縮放。演示的demo主要有以下幾個重點: 1,自定義旋轉動
本節引言: 好的,上一節中,我們又寫了一個關於Xfermode圖片混排的例子——擦美女衣服的Demo,加上前面的 利用Xfermode
1.首先集成的項目目錄我使用的是直接按照react-native init Project 的格式來導入的,也就是說,我的Android項目