編輯:關於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 ArrayList mItems;
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;
}
}
}
自定義控件三部曲之繪圖篇(十三)——Canvas與圖層(一)
在給大家講解了paint的幾個方法之後,我覺得有必要插一篇有關Canvas畫布的知識,在開始paint之前,我們講解了canvas繪圖的幾篇文章和cavas的save()
Android 3D旋轉動畫效果實現分解
這篇文章主要介紹一下如何實現View的3D旋轉效果,實現的主要原理就是圍繞Y軸旋轉,同時在Z軸方面上有一個深入的縮放。演示的demo主要有以下幾個重點: 1,自定義旋轉動
Android基礎入門教程——8.3.8 Paint API之—— Xfermode與PorterDuff詳解(五)
本節引言: 好的,上一節中,我們又寫了一個關於Xfermode圖片混排的例子——擦美女衣服的Demo,加上前面的 利用Xfermode
Android原生嵌入React Native詳解
1.首先集成的項目目錄我使用的是直接按照react-native init Project 的格式來導入的,也就是說,我的Android項目