編輯:關於Android編程
Android中關於控制開關和頁面/狀態切換的使用場景還是比較多的。源生做的支持也有比如RadioGroup 和Tabhost等。這裡准備通過自定義View來模仿學習下IOS兩種常見UI樣式: SwitchButton 和 SegmentControl。
首先先通過簡易的組裝View來實現兩種UI的相應效果,其次呢,嘗試通過繪制來達到同樣的更靈活的樣式。代碼前後共實現按鈕切換和頁面切換兩個樣式,三種實現方案,其中,兩種SwitchButton實現,一種SegmentControl實現。實現方案中關於自定義View繪制,本篇只講述SwitchView,希望大家能舉一反三,同樣做到SegmentControl的相同效果。個人也更傾向於使用自定義實現,更方便靈活。
先看效果圖:
頭部即為切換頁面的SegmentControl,然後第一行是通過組裝view來實現SwitchButton,第二行則是完全繪制出來的SwitchButton效果。接下來我們分別一一講述代碼實現。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxociAvPg0KPGgyIGlkPQ=="2switchbutton樣式兩種實現">2.SwitchButton樣式兩種實現
狀態開關按鈕常用於某些控制開關,設置選項裡最為常見。
該方法比較簡單明了,定義三個view,開啟狀態和關閉狀態兩個背景View,一個圓形按鈕view。點擊時候利用滑動動畫移動按鈕和狀態背景,達到類似的視覺效果。
先看xml布局:
<framelayout android:layout_height="wrap_content" android:layout_width="wrap_content">
</framelayout>
因為是幀布局,所以頂層使用merge(merge簡化xml不解釋,自行百度)。然後使用兩個開關狀態背景和一個圓形按鈕組合而成。
public class SwitchView extends FrameLayout {
protected boolean isChecked; //是否選中狀態
protected View onBgView;
protected View offBgView;
protected View circleView;
protected boolean autoForPerformClick = true; //是否允許點擊自動切換
protected OnCheckedChangedListener onCheckedChangedListener; //切換事件監聽
//...
}
一般狀態切換是由click事件監聽,根據業務邏輯來判斷是否切換狀態。但對於switchButton,通常我們操作時直觀感受應該是先切換了狀態才執行相應操作的,所以我們在performClick事件中直接根據autoForPerformClick 的狀態來相應點擊操作。
至於performClick ,其實就是控制條用onClickListener的方法體,具體邏輯在View源碼中查看。
public SwitchView(Context context) {
super(context);
initialize();
}
public SwitchView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public SwitchView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
protected void initialize() {
setClickable(true);
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutInflater.inflate(R.layout.switch_view, this);
onBgView = findViewById(R.id.on_bg_view);
offBgView = findViewById(R.id.off_bg_view);
circleView = findViewById(R.id.circle_view);
}
@Override
public boolean performClick() {
if (!autoForPerformClick) //如果不是自動響應則調用默認處理方法
return super.performClick();
/**
*否則直接切換switch狀態並觸發事件監聽
*/
setChecked(!isChecked, true);
if (onCheckedChangedListener != null) {
onCheckedChangedListener.onChanged(this, isChecked);
}
return super.performClick();
}
View點擊後會執行performClick方法,並判斷是否調用clickLisentener。這裡我們直接重寫performClick方法,如果自動響應autoForPerformClick為ture則直接切換Switch狀態,否則調用默認處理邏輯。
點擊打開,則圓形按鈕從左端滑動到右端,onBg顯示,offBg隱藏;
再點擊關閉,圓形按鈕從右端滑動到左端,onBg隱藏,offBg顯示。
public void setChecked(boolean value, boolean needAnimate) {
if (isChecked == value)
return;
isChecked = value;
float targetX = 0; //要移動的目標位置
if (getWidth() != 0) { //當前view沒有渲染上去時候,getWidth()為零
targetX = getWidth() - circleView.getWidth();
} else {
measure(0, 0);
targetX = getMeasuredWidth() - circleView.getMeasuredWidth();
}
long durationMillis = needAnimate ? 200 : 0;
if (isChecked) {
onBgView.bringToFront(); //顯示在最前端
onBgView.setVisibility(View.VISIBLE);
offBgView.setVisibility(View.VISIBLE);
//平移動畫
TranslateAnimation an1 = new TranslateAnimation(0, targetX, 0, 0);
an1.setFillAfter(true);
an1.setDuration(durationMillis);
circleView.startAnimation(an1);
//透明度動畫
AlphaAnimation an2 = new AlphaAnimation(0, 1);
an2.setFillAfter(true);
an2.setDuration(durationMillis);
onBgView.startAnimation(an2);
} else {
offBgView.bringToFront();
onBgView.setVisibility(View.VISIBLE);
offBgView.setVisibility(View.VISIBLE);
TranslateAnimation an1 = new TranslateAnimation(targetX, 0, 0, 0);
an1.setFillAfter(true);
an1.setDuration(durationMillis);
circleView.startAnimation(an1);
AlphaAnimation an2 = new AlphaAnimation(0, 1);
an2.setFillAfter(true);
an2.setDuration(durationMillis);
offBgView.startAnimation(an2);
}
}
狀態切換的兩個參數,value是否打開狀態,needAnimate是否需要動畫(否則直接切換效果)。setFillAfter保留動畫結束狀態,但並不影響View本身位置和狀態。切換時,先將當前顯示背景移動到最前端,其次添加按鈕動畫和漸隱動畫。
至此,最基本的組合View實現已經完成了。想要了解詳情的請在源碼中查看。源碼分為兩部分,一個項目是View的實現lib,另一塊是示例演示demo.
由於該樣式並不十分復雜,所以可以通過基本的圖形繪制draw出同樣的效果。
具體實現邏輯:通過自定view屬性來確定按鈕大小和中間圓鈕大小,在測量onMesure方法中控制測量值mode和Size,並在onLayout方法中得到圓鈕半徑和起始點位置。然後進行繪制,先繪制底部on圓角矩形背景,再繪制off漸變縮放的圓角矩形,最後繪制spot圓鈕。
嘴比較笨拙,又不會畫圖。用word的圖形工具將就畫下可以看就好了。
具體實現大體都類似,這裡貼上主要部分代碼
public class SwitchButton extends View{
/** */
private float radius;
/** 開啟顏色*/
private int onColor = Color.parseColor(#4ebb7f);
/** 關閉顏色*/
private int offBorderColor = Color.parseColor(#dadbda);
/** 灰色帶顏色*/
private int offColor = Color.parseColor(#ffffff);
/** 手柄顏色*/
private int spotColor = Color.parseColor(#ffffff);
/** 邊框顏色*/
private int borderColor = offBorderColor;
/** 畫筆*/
private Paint paint ;
/** 開關狀態*/
private boolean toggleOn = false;
/** 邊框大小*/
private int borderWidth = 2;
/** 垂直中心*/
private float centerY;
/** 按鈕的開始和結束位置*/
private float startX, endX;
/** 手柄X位置的最小和最大值*/
private float spotMinX, spotMaxX;
/**手柄大小 */
private int spotSize ;
/** 手柄X位置*/
private float spotX;
/** 關閉時內部灰色帶高度*/
private float offLineWidth;
/** */
private RectF rect = new RectF();
/** 默認使用動畫*/
private boolean defaultAnimate = true;
private OnSwitchChanged listener;
//...
}
讀取自定義屬性並賦值。講了又講的東西,略。
在onMeasure方法中根據給定mode和size來限定View,如果高寬不為明確值(UNSPECIFIED/AT_MOST),則定義自身高寬為明確值。 關於MeasureSpec的詳細講解,這裡附上愛哥的一篇文章–MeasureSpec,深入到賦值讀取的內部,不妨試著深入研究下。當然,更直接的方法就是點開源碼一探究竟咯。
onLayout方法中取得view的實際高寬,計算出圓角矩形半徑,圓鈕半徑以及起始點x方向位置。還有On矩形和off矩形的寬度。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
/**
*如果高寬未指定,則使用內置高寬明確大小
*/
Resources r = Resources.getSystem();
if(widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST){
widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, r.getDisplayMetrics());
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
}
if(heightMode == MeasureSpec.UNSPECIFIED || heightSize == MeasureSpec.AT_MOST){
heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, r.getDisplayMetrics());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
final int width = getWidth();
final int height = getHeight();
/**
*測量相應大小
*/
radius = Math.min(width, height) * 0.5f;
centerY = radius;
startX = radius;
endX = width - radius;
spotMinX = startX + borderWidth;
spotMaxX = endX - borderWidth;
spotSize = height - 4 * borderWidth;
spotX = toggleOn ? spotMaxX : spotMinX;
offLineWidth = 0;
}
前三步完成基本賦值之後,開始設置和綁定相應事件。這裡不作為重點部分也省略,主要講一下繪制過程和核心控制邏輯。
按照前面的簡易示例圖來繪制我們的ui圖。
@Override
public void draw(Canvas canvas) {
//繪制on背景
rect.set(0, 0, getWidth(), getHeight());
paint.setColor(borderColor);
canvas.drawRoundRect(rect, radius, radius, paint);
//繪制off背景(縮放至0時候不繪制)
if(offLineWidth > 0){
final float cy = offLineWidth * 0.5f;
rect.set(spotX - cy, centerY - cy, endX + cy, centerY + cy);
paint.setColor(offColor);
canvas.drawRoundRect(rect, cy, cy, paint);
}
//繪制圓鈕輪廓border
rect.set(spotX - 1 - radius, centerY - radius, spotX + 1.1f + radius, centerY + radius);
paint.setColor(borderColor);
canvas.drawRoundRect(rect, radius, radius, paint);
//繪制圓鈕
final float spotR = spotSize * 0.5f;
rect.set(spotX - spotR, centerY - spotR, spotX + spotR, centerY + spotR);
paint.setColor(spotColor);
canvas.drawRoundRect(rect, spotR, spotR, paint);
}
及诶按來便是我們的狀態切換動畫控制邏輯,即點擊按鈕之後setToggleOn或者setToggleOff執行的相應動作。
/**
* 執行效果,如果animate為true表示有動畫效果
* 否則直接執行計算並顯示最終打開1或者關閉0的效果繪制
*/
private void takeEffect(boolean animate) {
if(animate){
slide();
}else{
calculateEffect(toggleOn ? 1 : 0);
}
}
/**
*這裡偷個懶,直接使用空的animation,根據當前interpolatedTime(0~1)漸變過程來繪制不同階段的View,達到動畫效果
*當然,也可以開啟個線程或者定時任務,來實現從0到1的變換,勁兒改變視圖繪制過程
*/
private void slide(){
Animation animation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime,
Transformation t) {
if(toggleOn){
calculateEffect(interpolatedTime);
}else{
calculateEffect(1-interpolatedTime);
}
}
};
animation.setDuration(200);
clearAnimation();
startAnimation(animation);
}
/**
*計算繪制位置
*mapValueFromRangeToRange方法計算從當前位置相對於目標位置所對應的值
*通過顏色變化來達到透明度動畫效果(顏色漸變)
*/
private void calculateEffect(final double value) {
final float mapToggleX = (float) mapValueFromRangeToRange(value, 0, 1, spotMinX, spotMaxX);
spotX = mapToggleX;
float mapOffLineWidth = (float) mapValueFromRangeToRange(1 - value, 0, 1, 10, spotSize);
offLineWidth = mapOffLineWidth;
final int fb = Color.blue(onColor);
final int fr = Color.red(onColor);
final int fg = Color.green(onColor);
final int tb = Color.blue(offBorderColor);
final int tr = Color.red(offBorderColor);
final int tg = Color.green(offBorderColor);
int sb = (int) mapValueFromRangeToRange(1 - value, 0, 1, fb, tb);
int sr = (int) mapValueFromRangeToRange(1 - value, 0, 1, fr, tr);
int sg = (int) mapValueFromRangeToRange(1 - value, 0, 1, fg, tg);
sb = clamp(sb, 0, 255);
sr = clamp(sr, 0, 255);
sg = clamp(sg, 0, 255);
borderColor = Color.rgb(sr, sg, sb);
postInvalidate();
}
以上就是自定義View繪制的核心代碼,詳細查看源碼SwitchButton。相較於組合方法,它更便捷,也有更高的靈活性和擴展性。同時還不需要圖片資源支持。
常見的Tab有很多種,這裡使用的是IOS常見的一種切換效果SegmentControl。本篇只用最簡單的拼裝View實現類似效果。有興趣的可以自己嘗試繪制達到更優效果。(有空的話也會在後邊放出)
通過view組合生成 最近單的方案,沒有之一。使用現成的selector和背景來控制顯示效果。各個子view分別繼承 RelativeLayout並實現OnClick接口。最後在Segment中控制顯示和點擊切換。 自定義View繪制生成 這裡只是提供思路。定義一個ItemView,根據在Segment中位置揮之不同效果。背景效果會用selector.xml的都知道,使用shape標簽產生的drawable對象,其實就是一個GradientDrawable。所以我們自定義view可以直接通過使用GradientDrawable的setCornerRadii(float[] radii) 來繪制同樣的背景效果,勁兒可以做到不同顏色。最後,使用一個ViewGroup不含這些item即可。通過click事件來切換tab就可以了。 首先,類似的定義一個可點擊的通用的RelativLayout。(實現 Checkable接口使其可被選中也移除選中狀態,詳細可以參考前面的博文 微博/動態 點贊效果)。這裡涉及三個新內容,稍微說明講解下。
- checkMode 選中模式,是單選 CHECKMODE_CHECK 還是 CHECKMODE_RADIO 單選效果。使我們的自定義RelativeLayout可以做到單選和復選。
- onInitializeAccessibilityEvent 添加View接受事件源信息。即訂閱checked事件。由於事件可能由內部子view點擊觸發,所以這裡應該接收並處理相應的checked事件。當然,使用該方法首先要重寫onInitializeAccessibilityNodeInfo方法,添加我們關注的狀態信息。
- SavedState狀態保存 當我們內部可能嵌套復雜view的時候,為了防止數據狀態丟失,一般需要定義狀態保存類,用以保存和恢復當前View狀態。
#### 1.可點擊的通用RelativeLayout
繼承實現Clickable接口,簡要略過。
//定義checked狀態
public static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };
//重寫SetChecked方法和isChecked方法略
/**
*根據當前選擇模式checkMode 來控制單復選
*/
@Override
public boolean performClick() {
if (checkMode == CHECKMODE_CHECK) {
toggle();
} else if (checkMode == CHECKMODE_RADIO) {
setChecked(true);
}
return super.performClick();
}
/**
*添加Drawable 的checked狀態 ,並再繪制view是繪制相應狀態效果
*/
@Override
public int[] onCreateDrawableState(int extraSpace) {
int[] states = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(states, CHECKED_STATE_SET);
}
return states;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
Drawable drawable = getBackground();
if (drawable != null) {
int[] myDrawableState = getDrawableState();
drawable.setState(myDrawableState);
invalidate();
}
}
接受checked狀態事件信息
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(CheckedRelativeLayout.class.getName());
event.setChecked(checked);
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(CheckedRelativeLayout.class.getName());
info.setCheckable(true);
info.setChecked(checked);
}
保存View狀態和恢復
@Override
public Parcelable onSaveInstanceState() {//保存
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.checked = isChecked();
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {//恢復
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setChecked(ss.checked);
requestLayout();
}
用於保存數據的基本狀態類型
static class SavedState extends BaseSavedState {
boolean checked;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
checked = (Boolean) in.readValue(null);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeValue(checked);
}
public static final Creator CREATOR = new Creator() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
@Override
public String toString() {
return CompoundButton.SavedState{ + Integer.toHexString(System.identityHashCode(this)) + checked= + checked + };
}
代碼比較易於理解,這裡直接貼出來查閱即可。
基本思路,水平線性布局包裹對應左中右不同item個數的選項,並通過設置對應left/right/center來設置背景。然後分別為每個Item設置同一個點擊事件,點擊之後檢查是否當前item被選中,改變statu,同時出發切換事件。詳細代碼:
public class SegmentView extends LinearLayout {
protected final static int SEGMENT_LEFT_BG = R.drawable.segment_left_selector;
protected final static int SEGMENT_CENTER_BG = R.drawable.segment_center_selector;
protected final static int SEGMENT_RIGHT_BG = R.drawable.segment_right_selector;
protected int leftBg = SEGMENT_LEFT_BG;
protected int centerBg = SEGMENT_CENTER_BG;
protected int rightBg = SEGMENT_RIGHT_BG;
protected CheckedRelativeLayout2[] checkedRelativeLayouts;
protected int index = -1;
protected float textSize = -1;
protected int textColorN = Color.BLACK, textColorP = Color.BLACK;
protected OnIndexChangedListener onIndexChangedListener;
public SegmentView(Context context) {
super(context);
initialize();
}
public SegmentView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
initFromAttributes(context, attrs);
}
public SegmentView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
initFromAttributes(context, attrs);
}
protected void initialize() {
setGravity(Gravity.CENTER);
}
protected void initFromAttributes(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SegmentView);
String content = a.getString(R.styleable.SegmentView_content);
index = a.getInt(R.styleable.SegmentView_index, index);
textSize = a.getDimension(R.styleable.SegmentView_textSize, textSize);
textColorN = a.getColor(R.styleable.SegmentView_textColorN, textColorN);
textColorP = a.getColor(R.styleable.SegmentView_textColorP, textColorP);
leftBg = a.getResourceId(R.styleable.SegmentView_leftBg, leftBg);
centerBg = a.getResourceId(R.styleable.SegmentView_centerBg, centerBg);
rightBg = a.getResourceId(R.styleable.SegmentView_rightBg, rightBg);
a.recycle();
if (!TextUtils.isEmpty(content)) {
String[] contentStrings = content.split(,);
setContent(contentStrings);
}
setIndex(index);
}
public void setContent(String... content) {
View[] views = new View[content.length];
for (int i = 0, len = content.length; i < len; i++) {
String s = content[i];
TextView tv = new TextView(getContext());
tv.setTextColor(textColorN);
tv.setText(s);
if (textSize != -1) {
tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}
views[i] = tv;
}
setContent(views);
}
public void setContent(View... content) {
removeAllViews();
int lastIndex = content.length - 1;
checkedRelativeLayouts = new CheckedRelativeLayout2[content.length];
checkedRelativeLayouts[0] = createLeftView(content[0]);
checkedRelativeLayouts[lastIndex] = createRightView(content[lastIndex]);
for (int i = 1; i < lastIndex; i++) {
checkedRelativeLayouts[i] = createCenterView(content[i]);
}
for (View view : checkedRelativeLayouts) {
LayoutParams llp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
llp.weight = 1;
addView(view, llp);
}
}
public int getIndex() {
return index;
}
public void setIndex(int i) {
if (i < 0)
return;
checkedRelativeLayouts[i].setChecked(true);
}
public void setTextColorN(int textColorN) {
this.textColorN = textColorN;
}
public void setTextColorP(int textColorP) {
this.textColorP = textColorP;
}
protected CheckedRelativeLayout.OnCheckedChangeListener checkedChangeListener = new CheckedRelativeLayout.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CheckedRelativeLayout layout, boolean isChecked) {
if (isChecked) {
for (CheckedRelativeLayout2 item : checkedRelativeLayouts) {
if (!item.equals(layout)) {
item.setChecked(false);
}
}
if (onIndexChangedListener != null) {
int i = indexOf(checkedRelativeLayouts, layout);
index = i;
if (onIndexChangedListener != null) {
onIndexChangedListener.onChanged(SegmentView.this, index);
}
}
}
}
};
protected CheckedRelativeLayout2 createLeftView(View contentView) {
CheckedRelativeLayout2 layout = new CheckedRelativeLayout2(getContext());
layout.setBackgroundResource(leftBg);
layout.setGravity(Gravity.CENTER);
layout.addView(contentView);
layout.setOnCheckedChangeListener(checkedChangeListener);
return layout;
}
protected CheckedRelativeLayout2 createCenterView(View contentView) {
CheckedRelativeLayout2 layout = new CheckedRelativeLayout2(getContext());
layout.setBackgroundResource(centerBg);
layout.setGravity(Gravity.CENTER);
layout.addView(contentView);
layout.setOnCheckedChangeListener(checkedChangeListener);
return layout;
}
protected CheckedRelativeLayout2 createRightView(View contentView) {
CheckedRelativeLayout2 layout = new CheckedRelativeLayout2(getContext());
layout.setBackgroundResource(rightBg);
layout.setGravity(Gravity.CENTER);
layout.addView(contentView);
layout.setOnCheckedChangeListener(checkedChangeListener);
return layout;
}
public void setOnIndexChangedListener(OnIndexChangedListener l) {
this.onIndexChangedListener = l;
}
protected class CheckedRelativeLayout2 extends CheckedRelativeLayout {
protected TextView textView;
public CheckedRelativeLayout2(Context context) {
super(context);
}
@Override
public void addView(View child) {
super.addView(child);
if (child instanceof TextView) {
textView = (TextView) child;
}
}
@Override
public void setChecked(boolean checked) {
super.setChecked(checked);
if (textView != null) {
if (checked) {
textView.setTextColor(textColorP);
} else {
textView.setTextColor(textColorN);
}
}
}
}
public static interface OnIndexChangedListener {
public void onChanged(SegmentView view, int index);
}
public static int indexOf(T[] array, T obj) {
for (int i = 0, len = array.length; i < len; i++) {
if (array[i].equals(obj))
return i;
}
return -1;
}
}
該方法比較簡陋,背景顏色定制性不高。即只能通過既定drawable北京來實現。不過,其實是可以通過selector來定義相關背景drawable的。不妨試一下。
本來此方法只是簡單提及的一個想法而已,今天有空就一並寫了。時間匆忙,代碼稍微有些混亂,不過還是能起到一定示范效用的,這裡也貼出來供大家參考。
整體思路:
定義子item 設置其選中狀態和字體/背景色。通過測量方法保證顯示范圍和字體大小,通過GradientDrawable繪制圓角背景,並畫對應字體。
定義Segment 繼承自ViewGroup,讀取自定義屬性,根據文本內容添加子View。然後重寫OnMeasure方法和OnLayout方法來測量和布局子View。最後添加點擊事件,提供監聽接口。
代碼如下:
import com.qiao.demo.R;
import com.qiao.demo.R.styleable;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
public class SegmentView extends ViewGroup implements OnClickListener{
private final float r = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());
private int bgColor = 0xff0072c6;
private int fgColor = Color.WHITE;
private float mTextSize = 3f*r;
private String []mText= {item1,item2,item3};
private int checkedItem=1;
private OnItemClickListener listener;
public SegmentView(Context context) {
super(context);
initFromAttributes(context, null);
initalize();
}
public SegmentView(Context context, AttributeSet attrs) {
super(context, attrs);
initFromAttributes(context,attrs);
initalize();
}
protected void initFromAttributes(Context context, AttributeSet attrs) {
if(attrs==null) return;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SegmentView0);
String content = a.getString(R.styleable.SegmentView0_content0);
if(!isEmpty(content)){
mText = content.split(,);
}
checkedItem = a.getInt(R.styleable.SegmentView0_index0, checkedItem);
mTextSize = a.getDimension(R.styleable.SegmentView0_textSize0, mTextSize);
bgColor = a.getColor(R.styleable.SegmentView0_bgColor, bgColor);
fgColor = a.getColor(R.styleable.SegmentView0_textColor, fgColor);
a.recycle();
}
public void initalize(){
int length = mText.length;
for(int i=0;i=0){
maxWidth = widthSize/count;
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth,widthMode);
}
for(int i=0;i=0){
float textSize = Math.min(mTextSize,height-2*r);
if(width>0){
textSize = Math.min(textSize,(width-2*r)*2/text.length()); //英文比中文短(中文為兩個字符),故取mText.length()/2作為平均寬度
}
if(textSize != mTextSize ){
mTextPaint.setTextSize(textSize);
mTextPaint.getTextBounds(text, 0, text.length(), mTextBound);
}
}
}
@Override
public void draw(Canvas canvas) {
Rect rect = canvas.getClipBounds();
drawable.setBounds(new Rect(rect));
drawable.draw(canvas);
int l = (rect.width() - mTextBound.width())/2;
int b = (rect.height() + mTextBound.height())/2;
canvas.drawText(text, l, b, mTextPaint);
}
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener){
this.listener = onItemClickListener;
}
interface OnItemClickListener{
void onItemClick(ItemView item,int checkedItem);
}
public static boolean isEmpty(String str){
return null==str || str.trim().length() == 0;
}
}
參照前面兩段講述完全可以理解了。使用時候可以方便的通過自定義屬性來控制字體顏色和點擊背景。可以動態變更View高寬。有問題的同學可以在文末提出或指正。
感覺自己學習進步的速度很慢,常常伴隨著焦急浮躁。這篇文章也是積累了好久才慢吞吞的寫完了。代碼方面,個人也有不少不良習慣,助事業不夠清晰,不過總體上不是有礙觀瞻吧。
同樣的東西,嘗試用不同想法寫兩遍,我覺得是有好處的。至少於我,能看到不少有意思的東西。
最後, 附上本文的 示例源碼 . 由於資源上傳較早,第二部分的自定義View並沒有打包上傳。不過上便已經貼出完整代碼了,可以直接拿來使用。
後邊在考慮是寫一寫非UI層面的東西,還是繼續寫關於常見的增刪改UI界面。待定,總之,fighting..
分析android Activity啟動流程中ActivityManagerService所扮演的角色一、概述上一篇文章startActivit
本文實例講述了Android編程之繪制文本(FontMetrics)實現方法。分享給大家供大家參考,具體如下:Canvas 作為繪制文本時,使用FontMetrics對象
(一)前言Binder原本是IPC工具,但是在Android中它的主要作用是支持RPC(Remote Procedure Call),使得當前進程調用另一個進程的函數就像
經過了幾天的痛苦煎熬,終於把微信支付調通,整個調試過程很痛苦,痛苦的主要來源是微信支付的調試真的是,以前調試公眾號支付也是一波三折啊。好吧,開始!首先說明,我這裡主要沒有