當前位置:首頁 » 安卓系統 » androiddeviceattr

androiddeviceattr

發布時間: 2023-08-30 23:18:49

1. android自定義控制項,一般遵循什麼樣的原則

開發自定義控制項的步驟:
1、了解View的工作原理
2、 編寫繼承自View的子類
3、 為自定義View類增加屬性
4、 繪制控制項
5、 響應用戶消息
6 、自定義回調函數

一、View結構原理
Android系統的視圖結構的設計也採用了組合模式,即View作為所有圖形的基類,Viewgroup對View繼承擴展為視圖容器類。

View定義了繪圖的基本操作
基本操作由三個函數完成:measure()、layout()、draw(),其內部又分別包含了onMeasure()、onLayout()、onDraw()三個子方法。具體操作如下:
1、measure操作
measure操作主要用於計算視圖的大小,即視圖的寬度和長度。在view中定義為final類型,要求子類不能修改。measure()函數中又會調用下面的函數:
(1)onMeasure(),視圖大小的將在這里最終確定,也就是說measure只是對onMeasure的一個包裝,子類可以覆寫onMeasure()方法實現自己的計算視圖大小的方式,並通過setMeasuredDimension(width, height)保存計算結果。

2、layout操作
layout操作用於設置視圖在屏幕中顯示的位置。在view中定義為final類型,要求子類不能修改。layout()函數中有兩個基本操作:
(1)setFrame(l,t,r,b),l,t,r,b即子視圖在父視圖中的具體位置,該函數用於將這些參數保存起來;
(2)onLayout(),在View中這個函數什麼都不會做,提供該函數主要是為viewGroup類型布局子視圖用的;

3、draw操作
draw操作利用前兩部得到的參數,將視圖顯示在屏幕上,到這里也就完成了整個的視圖繪制工作。子類也不應該修改該方法,因為其內部定義了繪圖的基本操作:
(1)繪制背景;
(2)如果要視圖顯示漸變框,這里會做一些准備工作;
(3)繪制視圖本身,即調用onDraw()函數。在view中onDraw()是個空函數,也就是說具體的視圖都要覆寫該函數來實現自己的顯示(比如TextView在這里實現了繪制文字的過程)。而對於ViewGroup則不需要實現該函數,因為作為容器是「沒有內容「的,其包含了多個子view,而子View已經實現了自己的繪制方法,因此只需要告訴子view繪制自己就可以了,也就是下面的dispatchDraw()方法;
(4)繪制子視圖,即dispatchDraw()函數。在view中這是個空函數,具體的視圖不需要實現該方法,它是專門為容器類准備的,也就是容器類必須實現該方法;
(5)如果需要(應用程序調用了setVerticalFadingEdge或者setHorizontalFadingEdge),開始繪制漸變框;
(6)繪制滾動條;
從上面可以看出自定義View需要最少覆寫onMeasure()和onDraw()兩個方法。

二、View類的構造方法

創建自定義控制項的3種主要實現方式:
1)繼承已有的控制項來實現自定義控制項: 主要是當要實現的控制項和已有的控制項在很多方面比較類似, 通過對已有控制項的擴展來滿足要求。
2)通過繼承一個布局文件實現自定義控制項,一般來說做組合控制項時可以通過這個方式來實現。
注意此時不用onDraw方法,在構造廣告中通過inflater載入自定義控制項的布局文件,再addView(view),自定義控制項的圖形界面就載入進來了。
3)通過繼承view類來實現自定義控制項,使用GDI繪制出組件界面,一般無法通過上述兩種方式來實現時用該方式。

View(Context context)
Simple constructor to use when creating a view from code.
View(Context context, AttributeSet attrs)
Constructor that is called when inflating a view from XML.
View(Context context, AttributeSet attrs, int defStyle)
Perform inflation from XML and apply a class-specific base style.

三、自定義View增加屬性的兩種方法:
1)在View類中定義。通過構造函數中引入的AttributeSet 去查找XML布局的屬性名稱,然後找到它對應引用的資源ID去找值。
案例:實現一個帶文字的圖片(圖片、文字是onDraw方法重繪實現)

public class MyView extends View {

private String mtext;
private int msrc;

public MyView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}

public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
int resourceId = 0;

int textId = attrs.getAttributeResourceValue(null, "Text",0);
int srcId = attrs.getAttributeResourceValue(null, "Src", 0);

mtext = context.getResources().getText(textId).toString();

msrc = srcId;

}

@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub

Paint paint = new Paint();
paint.setColor(Color.RED);
InputStream is = getResources().openRawResource(msrc);

Bitmap mBitmap = BitmapFactory.decodeStream(is);

int bh = mBitmap.getHeight();
int bw = mBitmap.getWidth();

canvas.drawBitmap(mBitmap, 0,0, paint);

//canvas.drawCircle(40, 90, 15, paint);
canvas.drawText(mtext, bw/2, 30, paint);
}

}

布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<com.example.myimageview2.MyView
android:id="@+id/myView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
Text="@string/hello_world"
Src="@drawable/xh"/>

</LinearLayout>

屬性Text, Src在自定義View類的構造方法中讀取。

2)通過XML為View注冊屬性。與Android提供的標准屬性寫法一樣。
案例: 實現一個帶文字說明的ImageView (ImageView+TextView組合,文字說明,可在布局文件中設置位置)

public class MyImageView extends LinearLayout {

public MyImageView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}

public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
int resourceId = -1;
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.MyImageView);
ImageView iv = new ImageView(context);
TextView tv = new TextView(context);

int N = typedArray.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = typedArray.getIndex(i);

switch (attr) {
case R.styleable.MyImageView_Oriental:
resourceId = typedArray.getInt(
R.styleable.MyImageView_Oriental, 0);
this.setOrientation(resourceId == 1 ? LinearLayout.HORIZONTAL
: LinearLayout.VERTICAL);
break;

case R.styleable.MyImageView_Text:
resourceId = typedArray.getResourceId(
R.styleable.MyImageView_Text, 0);
tv.setText(resourceId > 0 ? typedArray.getResources().getText(
resourceId) : typedArray
.getString(R.styleable.MyImageView_Text));
break;

case R.styleable.MyImageView_Src:
resourceId = typedArray.getResourceId(
R.styleable.MyImageView_Src, 0);
iv.setImageResource(resourceId > 0 ?resourceId:R.drawable.ic_launcher);

break;

}

}

addView(iv);
addView(tv);
typedArray.recycle();

}

}

attrs.xml進行屬性聲明, 文件放在values目錄下
<?xml version="1.0" encoding="utf-8"?>
<resources>

<declare-styleable name="MyImageView">
<attr name="Text" format="reference|string"></attr>
<attr name="Oriental" >
<enum name="Horizontal" value="1"></enum>
<enum name="Vertical" value="0"></enum>
</attr>
<attr name="Src" format="reference|integer"></attr>
</declare-styleable>

</resources>

MainActivity的布局文件:先定義命名空間 xmlns:uview="http://schemas.android.com/apk/res/com.example.myimageview2"
然後可以像使用系統的屬性一樣使用:uview:Oriental="Vertical"

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:uview="http://schemas.android.com/apk/res/com.example.myimageview2"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />

<com.example.myimageview2.MyImageView
android:id="@+id/myImageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
uview:Text="這是一個圖片說明"
uview:Src="@drawable/tw"
uview:Oriental="Vertical">
</com.example.myimageview2.MyImageView>

</LinearLayout>

四、控制項繪制 onDraw()

五、

六、自定義View的方法
onFinishInflate() 回調方法,當應用從XML載入該組件並用它構建界面之後調用的方法
onMeasure() 檢測View組件及其子組件的大小
onLayout() 當該組件需要分配其子組件的位置、大小時
onSizeChange() 當該組件的大小被改變時
onDraw() 當組件將要繪制它的內容時
onKeyDown 當按下某個鍵盤時
onKeyUp 當松開某個鍵盤時
onTrackballEvent 當發生軌跡球事件時
onTouchEvent 當發生觸屏事件時
onWindowFocusChanged(boolean) 當該組件得到、失去焦點時
onAtrrachedToWindow() 當把該組件放入到某個窗口時
onDetachedFromWindow() 當把該組件從某個窗口上分離時觸發的方法
onWindowVisibilityChanged(int): 當包含該組件的窗口的可見性發生改變時觸發的方法

2. android 角標紅點怎麼設置

角標實現

要實現這個角標:

1、放置一個隱藏的圖片在app中,在需要它顯示的時候,顯示該圖片,並以該圖片為背景,顯示新增消息數;

2、自定義一個控制項,用於顯示該角標信息。例如,角標相對於控制項的位置、底色、數字等;

對比或者使用後,你會發現,自定義一個控制項,無疑更符合我們的使用習慣。無需特定的圖片,減少了app的大小等,更重要的是,這樣方便我們的使用。

在角標實現中,有個開源代碼BadgeView寫的很符合我們的心理預期。實現了我們常用的所有功能。

BadgeView

這是一個繼承TextView控制項,自定義而成的一個簡單控制項。我們通過它,可輕易實現對角標位置、角標底色、角標內容等控制。

java">
packagecom.readystatesoftware.viewbadger;

importandroid.content.Context;
importandroid.content.res.Resources;
importandroid.graphics.Color;
importandroid.graphics.Typeface;
importandroid.graphics.drawable.ShapeDrawable;
importandroid.graphics.drawable.shapes.RoundRectShape;
importandroid.util.AttributeSet;
importandroid.util.TypedValue;
importandroid.view.Gravity;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.view.ViewGroup.LayoutParams;
importandroid.view.ViewParent;
importandroid.view.animation.AccelerateInterpolator;
importandroid.view.animation.AlphaAnimation;
importandroid.view.animation.Animation;
importandroid.view.animation.DecelerateInterpolator;
importandroid.widget.FrameLayout;
importandroid.widget.TabWidget;
importandroid.widget.TextView;

/**
*"badge"toanygiven{@linkandroid.view.View}.
*outs.
*
*@authorJeffGilfelt
*/
{

publicstaticfinalintPOSITION_TOP_LEFT=1;
publicstaticfinalintPOSITION_TOP_RIGHT=2;
publicstaticfinalintPOSITION_BOTTOM_LEFT=3;
publicstaticfinalintPOSITION_BOTTOM_RIGHT=4;
publicstaticfinalintPOSITION_CENTER=5;

privatestaticfinalintDEFAULT_MARGIN_DIP=5;
privatestaticfinalintDEFAULT_LR_PADDING_DIP=5;
privatestaticfinalintDEFAULT_CORNER_RADIUS_DIP=8;
privatestaticfinalintDEFAULT_POSITION=POSITION_TOP_RIGHT;
privatestaticfinalintDEFAULT_BADGE_COLOR=Color.parseColor("#CCFF0000");//Color.RED;
privatestaticfinalintDEFAULT_TEXT_COLOR=Color.WHITE;

privatestaticAnimationfadeIn;
privatestaticAnimationfadeOut;

privateContextcontext;
privateViewtarget;

privateintbadgePosition;
privateintbadgeMarginH;
privateintbadgeMarginV;
privateintbadgeColor;

privatebooleanisShown;

privateShapeDrawablebadgeBg;

privateinttargetTabIndex;

publicBadgeView(Contextcontext){
this(context,(AttributeSet)null,android.R.attr.textViewStyle);
}

publicBadgeView(Contextcontext,AttributeSetattrs){
this(context,attrs,android.R.attr.textViewStyle);
}

/**
*Constructor-
*
*{@linkandroid.view.View}.
*
*@.
*@.
*/
publicBadgeView(Contextcontext,Viewtarget){
this(context,null,android.R.attr.textViewStyle,target,0);
}

/**
*Constructor-
*
*{@linkandroid.widget.TabWidget}
*tabatagivenindex.
*
*@.
*@.
*@.
*/
publicBadgeView(Contextcontext,TabWidgettarget,intindex){
this(context,null,android.R.attr.textViewStyle,target,index);
}

publicBadgeView(Contextcontext,AttributeSetattrs,intdefStyle){
this(context,attrs,defStyle,null,0);
}

publicBadgeView(Contextcontext,AttributeSetattrs,intdefStyle,Viewtarget,inttabIndex){
super(context,attrs,defStyle);
init(context,target,tabIndex);
}

privatevoidinit(Contextcontext,Viewtarget,inttabIndex){

this.context=context;
this.target=target;
this.targetTabIndex=tabIndex;

//applydefaults
badgePosition=DEFAULT_POSITION;
badgeMarginH=dipToPixels(DEFAULT_MARGIN_DIP);
badgeMarginV=badgeMarginH;
badgeColor=DEFAULT_BADGE_COLOR;

setTypeface(Typeface.DEFAULT_BOLD);
intpaddingPixels=dipToPixels(DEFAULT_LR_PADDING_DIP);
setPadding(paddingPixels,0,paddingPixels,0);
setTextColor(DEFAULT_TEXT_COLOR);

fadeIn=newAlphaAnimation(0,1);
fadeIn.setInterpolator(newDecelerateInterpolator());
fadeIn.setDuration(200);

fadeOut=newAlphaAnimation(1,0);
fadeOut.setInterpolator(newAccelerateInterpolator());
fadeOut.setDuration(200);

isShown=false;

if(this.target!=null){
applyTo(this.target);
}else{
show();
}

}

privatevoidapplyTo(Viewtarget){

LayoutParamslp=target.getLayoutParams();
ViewParentparent=target.getParent();
FrameLayoutcontainer=newFrameLayout(context);

if(targetinstanceofTabWidget){

//
target=((TabWidget)target).getChildTabViewAt(targetTabIndex);
this.target=target;

((ViewGroup)target).addView(container,
newLayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));

this.setVisibility(View.GONE);
container.addView(this);

}else{

//
ViewGroupgroup=(ViewGroup)parent;
intindex=group.indexOfChild(target);

group.removeView(target);
group.addView(container,index,lp);

container.addView(target);

this.setVisibility(View.GONE);
container.addView(this);

group.invalidate();

}

}

/**
*MakethebadgevisibleintheUI.
*
*/
publicvoidshow(){
show(false,null);
}

/**
*MakethebadgevisibleintheUI.
*
*@-inanimation.
*/
publicvoidshow(booleananimate){
show(animate,fadeIn);
}

/**
*MakethebadgevisibleintheUI.
*
*@.
*/
publicvoidshow(Animationanim){
show(true,anim);
}

/**
*Makethebadgenon-visibleintheUI.
*
*/
publicvoidhide(){
hide(false,null);
}

/**
*Makethebadgenon-visibleintheUI.
*
*@-outanimation.
*/
publicvoidhide(booleananimate){
hide(animate,fadeOut);
}

/**
*Makethebadgenon-visibleintheUI.
*
*@-visible.
*/
publicvoidhide(Animationanim){
hide(true,anim);
}

/**
*.
*
*/
publicvoidtoggle(){
toggle(false,null,null);
}

/**
*.
*
*@-in/outanimation.
*/
publicvoidtoggle(booleananimate){
toggle(animate,fadeIn,fadeOut);
}

/**
*.
*
*@.
*@-visible.
*/
publicvoidtoggle(AnimationanimIn,AnimationanimOut){
toggle(true,animIn,animOut);
}

privatevoidshow(booleananimate,Animationanim){
if(getBackground()==null){
if(badgeBg==null){
badgeBg=getDefaultBackground();
}
setBackgroundDrawable(badgeBg);
}
applyLayoutParams();

if(animate){
this.startAnimation(anim);
}
this.setVisibility(View.VISIBLE);
isShown=true;
}

privatevoidhide(booleananimate,Animationanim){
this.setVisibility(View.GONE);
if(animate){
this.startAnimation(anim);
}
isShown=false;
}

privatevoidtoggle(booleananimate,AnimationanimIn,AnimationanimOut){
if(isShown){
hide(animate&&(animOut!=null),animOut);
}else{
show(animate&&(animIn!=null),animIn);
}
}

/**
*Incrementthenumericbadgelabel.
*anintegervalue,itslabelwillbesetto"0".
*
*@paramoffsettheincrementoffset.
*/
publicintincrement(intoffset){
CharSequencetxt=getText();
inti;
if(txt!=null){
try{
i=Integer.parseInt(txt.toString());
}catch(NumberFormatExceptione){
i=0;
}
}else{
i=0;
}
i=i+offset;
setText(String.valueOf(i));
returni;
}

/**
*Decrementthenumericbadgelabel.
*anintegervalue,itslabelwillbesetto"0".
*
*@paramoffsetthedecrementoffset.
*/
publicintdecrement(intoffset){
returnincrement(-offset);
}

(){

intr=dipToPixels(DEFAULT_CORNER_RADIUS_DIP);
float[]outerR=newfloat[]{r,r,r,r,r,r,r,r};

RoundRectShaperr=newRoundRectShape(outerR,null,null);
ShapeDrawabledrawable=newShapeDrawable(rr);
drawable.getPaint().setColor(badgeColor);

returndrawable;

}

privatevoidapplyLayoutParams(){

FrameLayout.LayoutParamslp=newFrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);

switch(badgePosition){
casePOSITION_TOP_LEFT:
lp.gravity=Gravity.LEFT|Gravity.TOP;
lp.setMargins(badgeMarginH,badgeMarginV,0,0);
break;
casePOSITION_TOP_RIGHT:
lp.gravity=Gravity.RIGHT|Gravity.TOP;
lp.setMargins(0,badgeMarginV,badgeMarginH,0);
break;
casePOSITION_BOTTOM_LEFT:
lp.gravity=Gravity.LEFT|Gravity.BOTTOM;
lp.setMargins(badgeMarginH,0,0,badgeMarginV);
break;
casePOSITION_BOTTOM_RIGHT:
lp.gravity=Gravity.RIGHT|Gravity.BOTTOM;
lp.setMargins(0,0,badgeMarginH,badgeMarginV);
break;
casePOSITION_CENTER:
lp.gravity=Gravity.CENTER;
lp.setMargins(0,0,0,0);
break;
default:
break;
}

setLayoutParams(lp);

}

/**
*.
*
*/
publicViewgetTarget(){
returntarget;
}

/**
*?
*
*/
@Override
publicbooleanisShown(){
returnisShown;
}

/**
*.
*
*oneofPOSITION_TOP_LEFT,POSITION_TOP_RIGHT,POSITION_BOTTOM_LEFT,POSITION_BOTTOM_RIGHT,POSTION_CENTER.
*
*/
publicintgetBadgePosition(){
returnbadgePosition;
}

/**
*Setthepositioningofthisbadge.
*
*@_TOP_LEFT,POSITION_TOP_RIGHT,POSITION_BOTTOM_LEFT,POSITION_BOTTOM_RIGHT,POSTION_CENTER.
*
*/
publicvoidsetBadgePosition(intlayoutPosition){
this.badgePosition=layoutPosition;
}

/**
*.
*
*/
(){
returnbadgeMarginH;
}

/**
*.
*
*/
(){
returnbadgeMarginV;
}

/**
*Setthehorizontal/.
*
*@.
*/
publicvoidsetBadgeMargin(intbadgeMargin){
this.badgeMarginH=badgeMargin;
this.badgeMarginV=badgeMargin;
}

/**
*Setthehorizontal/.
*
*@paramhorizontalmargininpixels.
*@paramverticalmargininpixels.
*/
publicvoidsetBadgeMargin(inthorizontal,intvertical){
this.badgeMarginH=horizontal;
this.badgeMarginV=vertical;
}

/**
*.
*
*/
(){
returnbadgeColor;
}

/**
*.
*
*@.
*/
(intbadgeColor){
this.badgeColor=badgeColor;
badgeBg=getDefaultBackground();
}

privateintdipToPixels(intdip){
Resourcesr=getResources();
floatpx=TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dip,r.getDisplayMetrics());
return(int)px;
}

}

3. Android開發中,長按事件怎麼獲得屏幕坐標點

對於很多游戲使用屏幕控制一般需要考慮長按事件,比如在動作類的游戲中需要長按發射武器,結合Android Button模型,我們實現一個帶圖片的Button的長按,為了更清晰的顯示原理,Android開發網這里使用ImageButton作為基類.
public class RepeatingImageButton extends ImageButton {
private long mStartTime; //記錄長按開始
private int mRepeatCount; //重復次數計數
private RepeatListener mListener;
private long mInterval = 500; //Timer觸發間隔,即每0.5秒算一次按下

public RepeatingImageButton(Context context) {
this(context, null);
}
public RepeatingImageButton(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.imageButtonStyle);
}
public RepeatingImageButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setFocusable(true); //允許獲得焦點
setLongClickable(true); //啟用長按事件
}

public void setRepeatListener(RepeatListener l, long interval) { //實現重復按下事件listener
mListener = l;
mInterval = interval;
}

@Override
public boolean performLongClick() {
mStartTime = SystemClock.elapsedRealtime();
mRepeatCount = 0;
post(mRepeater);
return true;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) { // 本方法原理同onKeyUp的一樣,這里處理屏幕事件,下面的onKeyUp處理Android手機上的物理按鍵事件
removeCallbacks(mRepeater);
if (mStartTime != 0) {
doRepeat(true);
mStartTime = 0;
}
}
return super.onTouchEvent(event);
}
//處理導航鍵事件的中鍵或軌跡球按下事件
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
super.onKeyDown(keyCode, event);
return true;
}
return super.onKeyDown(keyCode, event);
}
//當按鍵彈起通知長按結束
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:

removeCallbacks(mRepeater); //取消重復listener捕獲
if (mStartTime != 0) {
doRepeat(true); //如果長按事件累計時間不為0則說明長按了
mStartTime = 0; //重置長按計時器
}
}
return super.onKeyUp(keyCode, event);
}

private Runnable mRepeater = new Runnable() { //在線程中判斷重復
public void run() {
doRepeat(false);
if (isPressed()) {
postDelayed(this, mInterval); //計算長按後延遲下一次累加
}
}
};
private void doRepeat(boolean last) {
long now = SystemClock.elapsedRealtime();
if (mListener != null) {
mListener.onRepeat(this, now - mStartTime, last ? -1 : mRepeatCount++);
}
}

下面是重復Button Listener介面的定義,調用時在Button中先使用setRepeatListener()方法實現RepeatListener介面
public interface RepeatListener {
void onRepeat(View v, long ration, int repeatcount); //參數一為用戶傳入的Button對象,參數二為延遲的毫秒數,第三位重復次數回調。
}
}

本類大家可以直接在自己的View中implements實現RepeatListener介面即可.

4. android 自定義switch樣式

修改後的MySwitch控制項介面基本與原Switch控制項一致,並且除了可支持所有SDK外,增加了2項小功能:
1. 支持用Track背景圖片的方式代替Texton Textoff等文字方式表現開關狀態
2.支持調整控制Switch的高度
下面貼出Switch修改的關鍵代碼:

/**
* <p>
* modified from android SDK 14(4.0) android.widget.Switch.
* <br/>
* <strong>new feature: </strong>
* <ol>
* <li>support SDK 1 or higher. </li>
* <li>you can use track drawable instead of text to display the changes of off-on state!</li>
* <li>you can control the Switch minimum height. </li>
* </ol>
* </p>
*
* @see {@link Switch}
* @author Wison
*/
public class MySwitch extends CompoundButton {
// Support track drawable instead of text
private Drawable mTrackOnDrawable;
private Drawable mTrackOffDrawable;

// Support minimum height
private int mSwitchMinHeight;

/**
* Construct a new Switch with a default style determined by the given theme attribute,
* overriding specific style attributes as requested.
*
* @param context The Context that will determine this widget's theming.
* @param attrs Specification of attributes that should deviate from the default styling.
* @param defStyle An attribute ID within the active theme containing a reference to the
* default style for this widget. e.g. android.R.attr.switchStyle.
*/
public MySwitch(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);

mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Resources res = getResources();
mTextPaint.density = res.getDisplayMetrics().density;
//float scaledDensity = res.getDisplayMetrics().scaledDensity;
//mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Switch, defStyle, 0);

// off-on 模式: 圖片模式或文字模式,圖片模式是用Track背景圖片表示off-on的狀態,文字模式是用文字來表示off-on狀態。
mTrackOnDrawable = a.getDrawable(R.styleable.Switch_trackOn);
mTrackOffDrawable = a.getDrawable(R.styleable.Switch_trackOff);
if (checkTrackOffOnDrawable()) {
// 如果設定圖片模式,則默認顯示off狀態
mTrackDrawable = mTrackOffDrawable;
} else {
mTrackDrawable = a.getDrawable(R.styleable.Switch_track);
}

mThumbDrawable = a.getDrawable(R.styleable.Switch_thumb);
mTextOn = a.getText(R.styleable.Switch_textOn);
mTextOff = a.getText(R.styleable.Switch_textOff);
mThumbTextPadding = a.getDimensionPixelSize(R.styleable.Switch_thumbTextPadding, 0);
mSwitchMinWidth = a.getDimensionPixelSize(R.styleable.Switch_switchMinWidth, 0);

mSwitchMinHeight = a.getDimensionPixelSize(R.styleable.Switch_switchMinHeight, 0);

mSwitchPadding = a.getDimensionPixelSize(R.styleable.Switch_switchPadding, 0);

int appearance = a.getResourceId(R.styleable.Switch_switchTextAppearance, 0);
if (appearance != 0) {
setSwitchTextAppearance(context, appearance);
}
a.recycle();

ViewConfiguration config = ViewConfiguration.get(context);
mTouchSlop = config.getScaledTouchSlop();
mMinFlingVelocity = config.getScaledMinimumFlingVelocity();

// Refresh display with current params
refreshDrawableState();
setChecked(isChecked());
}

private boolean checkTrackOffOnDrawable() {
return mTrackOnDrawable != null && mTrackOffDrawable != null;
}

@SuppressLint("NewApi")
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOnLayout == null) {
mOnLayout = makeLayout(mTextOn);
}
if (mOffLayout == null) {
mOffLayout = makeLayout(mTextOff);
}
mTrackDrawable.getPadding(mTempRect);
final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth());
final int switchWidth = Math.max(mSwitchMinWidth,
maxTextWidth * 2 + mThumbTextPadding * 4 + mTempRect.left + mTempRect.right);

// final int switchHeight = mTrackDrawable.getIntrinsicHeight();
int switchHeight;
if (mSwitchMinHeight <= 0) {
switchHeight = mTrackDrawable.getIntrinsicHeight();
} else {
switchHeight = Math.max(mSwitchMinHeight, mTempRect.top + mTempRect.bottom);
}
mThumbWidth = maxTextWidth + mThumbTextPadding * 2;

mSwitchWidth = switchWidth;
mSwitchHeight = switchHeight;

super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int measuredHeight = getMeasuredHeight();
if (measuredHeight < switchHeight) {
if (Build.VERSION.SDK_INT >= 11) {
setMeasuredDimension(getMeasuredWidthAndState(), switchHeight);
} else {
setMeasuredDimension(getMeasuredWidth(), switchHeight);
}
}
}
@Override
public void setChecked(boolean checked) {
if (checkTrackOffOnDrawable()) {
mTrackDrawable = checked ? mTrackOnDrawable : mTrackOffDrawable;
refreshDrawableState();
}
super.setChecked(checked);
mThumbPosition = checked ? getThumbScrollRange() : 0;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw the switch
int switchLeft = mSwitchLeft;
int switchTop = mSwitchTop;
int switchRight = mSwitchRight;
int switchBottom = mSwitchBottom;

if (checkTrackOffOnDrawable()) {
mTrackDrawable = getTargetCheckedState() ? mTrackOnDrawable : mTrackOffDrawable;
refreshDrawableState();
}

mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
mTrackDrawable.draw(canvas);
canvas.save()
mTrackDrawable.getPadding(mTempRect);
int switchInnerLeft = switchLeft + mTempRect.left;
int switchInnerTop = switchTop + mTempRect.top;
int switchInnerRight = switchRight - mTempRect.right;
int switchInnerBottom = switchBottom - mTempRect.bottom;
canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);

mThumbDrawable.getPadding(mTempRect);
final int thumbPos = (int) (mThumbPosition + 0.5f);
int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos;
int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right;

mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
mThumbDrawable.draw(canvas);

// mTextColors should not be null, but just in case
if (mTextColors != null) {
mTextPaint.setColor(mTextColors.getColorForState(getDrawableState(),
mTextColors.getDefaultColor()));
}
mTextPaint.drawableState = getDrawableState();

Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;

if (switchText != null) {
canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2,
(switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
switchText.draw(canvas);
}
canvas.restore();
}
}

下面是關鍵屬性聲明:
<declare-styleable name="Switch">

<!-- Drawable to use when the switch is in the checked/"on" state. -->
<attr name="trackOn" format="reference" />
<!-- Drawable to use when the switch is in the unchecked/"off" state. -->
<attr name="trackOff" format="reference" />
<!-- Minimum height for the switch component -->
<attr name="switchMinHeight" format="dimension" />

<!-- Drawable to use as the "thumb" that switches back and forth. -->
<attr name="thumb" format="reference" />
<!-- Drawable to use as the "track" that the switch thumb slides within. -->
<attr name="track" format="reference" />
<!-- Text to use when the switch is in the checked/"on" state. -->
<attr name="textOn" format="string" />
<!-- Text to use when the switch is in the unchecked/"off" state. -->
<attr name="textOff" format="string" />
<!-- Amount of padding on either side of text within the switch thumb. -->
<attr name="thumbTextPadding" format="dimension" />
<!-- TextAppearance style for text displayed on the switch thumb. -->
<attr name="switchTextAppearance" format="reference" />
<!-- Minimum width for the switch component -->
<attr name="switchMinWidth" format="dimension" />
<!-- Minimum space between the switch and caption text -->
<attr name="switchPadding" format="dimension" />
</declare-styleable>

5. android 自定義控制項 屬性怎麼用

自定義屬性設置
public class lei extends RelativeLayout {

private TextView tv1
public lei(Context context) {
super(context);
}
public lei(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.item,this);
tv1 = findViewById(R.id.tv1);
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.lei);
String str1 = (String) array.getText(R.styleable.lei_settitle);
int str2 = array.getColor(R.styleable.lei_setbackgroudcolor,Color.BLACK);
tv1.setText(str1);
tv1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getContext(), "toast", Toast.LENGTH_SHORT).show();
}
});
array.recycle();
}
}
布局控制項
<com.example.administrator.myapplication.lei
android:id="@+id/ll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
cs:setbackgroudcolor="#158616"
cs:settitle="這個是標題"
cs:settitletextcolor="#FFFFFF"
cs:settextrcolor="#FFFFFF"
>
</com.example.administrator.myapplication.lei>
屬性設置
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="lei" >
<attr name="setbackgroudcolor" format="color|reference"/>
<attr name="settitle" format="string|reference"/>
<attr name="settextrcolor" format="color"/>
<attr name="settitletextcolor" format="color"/>
</declare-styleable>

</resources>

6. 有哪些android開發技巧

1、android:clipToPadding

意思是控制項的繪制區域是否在padding裡面。默認為true。如果你設置了此屬性值為false,就能實現一個在布局上事半功陪的效果。先看一個效果圖。

上圖中的ListView頂部默認有一個間距,向上滑動後,間距消失,如下圖所示。

如果使用margin或padding,都不能實現這個效果。加一個headerView又顯得大材小用,而且過於麻煩。此處的clipToPadding配合paddingTop效果就剛剛好。

同樣,還有另外一個屬性也很神奇:android:clipChildren,具體請參考:【Android】神奇的android:clipChildren屬性

2、match_parent和wrap_content

按理說這兩個屬性一目瞭然,一個是填充布局空間適應父控制項,一個是適應自身內容大小。但如果在列表如ListView中,用錯了問題就大了。ListView中的getView方法需要計算列表條目,那就必然需要確定ListView的高度,onMesure才能做測量。如果指定了wrap_content,就等於告訴系統,如果我有一萬個條目,你都幫我計算顯示出來,然後系統按照你的要求就new了一萬個對象出來。那你不悲劇了?先看一個圖。

假設現在ListView有8條數據,match_parent需要new出7個對象,而wrap_content則需要8個。這里涉及到View的重用,就不多探討了。所以這兩個屬性的設置將決定getView的調用次數。

由此再延伸出另外一個問題:getView被多次調用。

什麼叫多次調用?比如position=0它可能調用了幾次。看似很詭異吧。GridView和ListView都有可能出現,說不定這個禍首就是wrap_content。說到底是View的布局出現了問題。如果嵌套的View過於復雜,解決方案可以是通過代碼測量列表所需要的高度,或者在getView中使用一個小技巧:parent.getChildCount == position

@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (parent.getChildCount() == position) {
// does things here
}

return convertView;
}

3、IllegalArgumentException: pointerIndex out of range

出現這個Bug的場景還是很無語的。一開始我用ViewPager + PhotoView(一個開源控制項)顯示圖片,在多點觸控放大縮小時就出現了這個問題。一開始我懷疑是PhotoView的bug,找了半天無果。要命的是不知如何try,老是crash。後來才知道是android遺留下來的bug,源碼里沒對pointer index做檢查。改源碼重新編譯不太可能吧。明知有exception,又不能從根本上解決,如果不讓它crash,那就只能try-catch了。解決辦法是:自定義一個ViewPager並繼承ViewPager。請看以下代碼:

/**
* 自定義封裝android.support.v4.view.ViewPager,重寫onInterceptTouchEvent事件,捕獲系統級別異常
*/
public class CustomViewPager extends ViewPager {

public CustomViewPager(Context context) {
this(context, null);
}

public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
try {
return super.onInterceptTouchEvent(ev);
} catch (IllegalArgumentException e) {
LogUtil.e(e);
} catch ( e) {
LogUtil.e(e);
}
return false;
}
}

把用到ViewPager的布局文件,替換成CustomViewPager就OK了。

4、ListView中item點擊事件無響應

listView的Item點擊事件突然無響應,問題一般是在listView中加入了button、checkbox等控制項後出現的。這個問題是聚焦沖突造成的。在android裡面,點擊屏幕之後,點擊事件會根據你的布局來進行分配的,當你的listView裡面增加了button之後,點擊事件第一優先分配給你listView裡面的button。所以你的點擊Item就失效了,這個時候你就要根據你的需求,是給你的item的最外層layout設置點擊事件,還是給你的某個布局元素添加點擊事件了。

解決辦法:在ListView的根控制項中設置(若根控制項是LinearLayout, 則在LinearLayout中加入以下屬性設置)descendantFocusability屬性。

android:descendantFocusability="blocksDescendants"

官方文檔也是這樣說明。

5、getSupportFragmentManager()和getChildFragmentManager()

有一個需求,Fragment需要嵌套3個Fragment。基本上可以想到用ViewPager實現。開始代碼是這樣寫的:

mViewPager.setAdapter(new CustomizeFragmentPagerAdapter(getActivity().getSupportFragmentManager(), subFragmentList));

導致的問題是嵌套的Fragment有時會莫名其妙不顯示。開始根本不知道問題出現在哪,當你不知道問題的原因時,去解決這個問題顯然比較麻煩。經過一次又一次的尋尋覓覓,終於在stackoverflow上看到了同樣的提問。說是用getChildFragmentManager()就可以了。真是這么神奇!

mViewPager.setAdapter(new CustomizeFragmentPagerAdapter(getChildFragmentManager, subFragmentList));

讓我們看一下這兩個有什麼區別。首先是getSupportFragmentManager(或者getFragmentManager)的說明:

Return the FragmentManager for interacting with fragments associated with this fragment's activity.

然後是getChildFragmentManager:

Return a private FragmentManager for placing and managing Fragments inside of this Fragment.

Basically, the difference is that Fragment's now have their own internal FragmentManager that can handle Fragments. The child FragmentManager is the one that handles Fragments contained within only the Fragment that it was added to. The other FragmentManager is contained within the entire Activity.

已經說得比較明白了。

6、ScrollView嵌套ListView

這樣的設計是不是很奇怪?兩個同樣會滾動的View居然放到了一起,而且還是嵌套的關系。曾經有一個這樣的需求:界面一共有4個區域部分,分別是公司基本信息(logo、名稱、法人、地址)、公司簡介、公司榮譽、公司口碑列表。每部分內容都需要根據內容自適應高度,不能寫死。鄙人首先想到的也是外部用一個ScrollView包圍起來。然後把這4部分分別用4個自定義控制項封裝起來。基本信息和公司簡介比較簡單,榮譽需要用到RecyclerView和TextView的組合,RecyclerView(當然,用GridView也可以,3列多行的顯示)存放榮譽圖片,TextView顯示榮譽名稱。最後一部分口碑列表當然是ListView了。這時候,問題就出來了。需要解決ListView放到ScrollView中的滑動問題和RecyclerView的顯示問題(如果RecyclerView的高度沒法計算,你是看不到內容的)。

當然,網上已經有類似的提問和解決方案了。

給一個網址:

四種方案解決ScrollView嵌套ListView問題

ListView的情況還比較好解決,優雅的做法無非寫一個類繼承ListView,然後重寫onMeasure方法。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}

ListView可以重寫onMeasure解決,RecyclerView重寫這個方法是行不通的。

說到底其實計算高度嘛。有兩種方式,一種是動態計算RecycleView,然後設置setLayoutParams;另外一種跟ListView的解決方式類似,定義一個類繼承LinearLayoutManager或GridLayoutManager(注意:可不是繼承RecyclerView),重寫onMeasure方法(此方法比較麻煩,此處不表,下次寫一篇文章再作介紹)。

動態計算高度如下:

int heightPx = DensityUtil.dip2px(getActivity(), (imageHeight + imageRowHeight) * lines);
MarginLayoutParams mParams = new MarginLayoutParams(LayoutParams.MATCH_PARENT, heightPx);
mParams.setMargins(0, 0, 0, 0);
LinearLayout.LayoutParams lParams = new LinearLayout.LayoutParams(mParams);
honorImageRecyclerView.setLayoutParams(lParams);

思路是這樣的:服務端返回榮譽圖片後,由於是3列顯示的方式,只需要計算需要顯示幾行,然後給定行間距和圖片的高度,再設置setLayoutParams就行了。

int lines = (int) Math.ceil(totalImages / 3d);

至此,這個奇怪的需求得到了解決。

可是在滑動的時候,感覺出現卡頓的現象。聰明的你肯定想到是滑動沖突了。應該是ScrollView的滑動干擾到了ListView的滑動。怎麼辦呢?能不能禁掉ScrollView的滑動?

網路一下,你肯定能搜索到答案的。先上代碼:

/**
* @author Leo
*
* Created in 2015-9-12
* 攔截ScrollView滑動事件
*/
public class CustomScrollView extends ScrollView {

private int downY;
private int touchSlop;

public CustomScrollView(Context context) {
this(context, null);
}

public CustomScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public CustomScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}

@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
int action = e.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downY = (int) e.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int moveY = (int) e.getRawY();
if (Math.abs(moveY - downY) > touchSlop) {
return true;
}
}
return super.onInterceptTouchEvent(e);
}
}

只要理解了getScaledTouchSlop()這個方法就好辦了。這個方法的注釋是:Distance in pixels a touch can wander before we think the user is scrolling。說這是一個距離,表示滑動的時候,手的移動要大於這個距離才開始移動控制項,如果小於此距離就不觸發移動。

看似很完美了。

但是還有另外一個問題:我每次載入這個界面花的時間太長了,每次由其它界面啟動這個界面時,都要卡上1~2秒,而且因手機性能時間不等。並不是由於網路請求,取數據由子線程做,跟UI線程毫無關系。這樣的體驗自己看了都很不爽。

幾天過去了,還是那樣。馬上要給老闆演示了。這樣的體驗要被罵十次呀。

難道跟ScrollView的嵌套有關?

好吧,那我重構代碼。不用ScrollView了。直接用一個ListView,然後add一個headerView存放其它內容。因為控制項封裝得還算好,沒改多少布局就OK了,一運行,流暢順滑,一切迎刃而解!

本來就是這么簡單的問題,為什麼非得用ScrollView嵌套呢?

stackoverflow早就告訴你了,不要這樣嵌套!不要這樣嵌套!不要這樣嵌套!重要的事情說三遍。

ListView inside ScrollView is not scrolling on Android

當然,從android 5.0 Lollipop開始提供了一種新的API支持嵌入滑動,此時,讓像這樣的需求也能很好實現。

此處給一個網址,大家有興趣自行了解,此處不再討論。

Android NestedScrolling 實戰

7、EmojiconTextView的setText(null)

這是開源表情庫com.rockerhieu.emojicon中的TextView加強版。相信很多人用到過這個開源工具包。TextView用setText(null)完全沒問題。但EmojiconTextView setText(null)後就悲劇了,直接crash,顯示的是null pointer。開始我懷疑時這個view沒初始化,但並不是。那就調試一下唄。

@Override
public void setText(CharSequence text, BufferType type) {
SpannableStringBuilder builder = new SpannableStringBuilder(text);
EmojiconHandler.addEmojis(getContext(), builder, mEmojiconSize);
super.setText(builder, type);
}

EmojiconTextView中的setText看來沒什麼問題。點SpannableStringBuilder進去看看,源碼原來是這樣的:

/**
* Create a new SpannableStringBuilder containing a of the
* specified text, including its spans if any.
*/
public SpannableStringBuilder(CharSequence text) {
this(text, 0, text.length());
}

好吧。問題已經找到了,text.length(),不空指針才怪。

text = text == null ? "" : text;
SpannableStringBuilder builder = new SpannableStringBuilder(text);

加一行判斷就行了。


先想到這么多,以後再補充。

7. Android8.1系統修改串口節點

        應客戶需求,需要將Android系統裡面的串口4(/dev/ttyS4)節點名稱修改為/dev/ttyS9。

        由於設備節點都是在驅動裡面注冊生成的於是查看串口驅動。根據log信息找到了系統串口驅動\kernel\drivers\tty\serial\8250\8250_dw.c中的注冊函數dw8250_probe。該函數中找到serial8250_register_8250_port,這個是注冊設備節點的函數。然後查看serial8250_register_8250_port函數定義(kernel\drivers\tty\serial\老山租8250\8250_core.c),找到了後續注冊節點的函數uart_add_one_port,繼續查看該函數(位於kernel\drivers\tty\serial\serial_core.c)定義,該函數又是調用tty_port_register_device_attr進行注冊的,繼續跟蹤該函數(侍兆kernel\drivers\tty\tty_port.c),該函數又是調用了tty_register_device_attr(位於kernel\drivers\tty\tty_io.c)進行注冊。添加log分析得知串口節點唯虛的名字獲取是由tty_line_name產生的。於是查看tty_line_name的實現:

static ssize_t tty_line_name(struct tty_driver *driver, int index, char *p)

{

if (driver->flags & TTY_DRIVER_UNNUMBERED_NODE)

return sprintf(p, "%s", driver->name);

else

return sprintf(p, "%s%d", driver->name,

  index + driver->name_base);

}

}

通過列印得知串口節點設備名稱是由驅動名稱 driver->name和串口序號index 組成的,於是添加判斷 driver->name是否為"ttyS",index是否為4,如果是就替換。

8. Android自定義屬性TypedArray詳解

大家好,我是程序員雙木L,後續會發專題類的文章,這是自定義控制項的第一篇,之後也會陸續更新相關的文章,歡迎關注。

自定義屬性在自定義控制項過程中屬於比較常見的操作,我們可以回想一下這樣的場景:自定義view的過程中,我們需要在不同的情況下設置不同的文字大小,那麼我們是不是就需要提供對外的方法來設置,這樣就比較靈活操作。而我們自定義對外的方法,就是我們自定義的屬性啦,那我們來分析一下其原理及作用。

下面我們根據例子來進行分析:

1、首先我們需要在res->values目錄下新建attrs.xml文件,該文件就是用來聲明屬性名及其接受的數據格式的,如下:

attr名詞解析:

name表示屬性名,上面的屬性名是我自己定義的。

format表示接受的輸入格式,format格式集合如下:

2、自定義屬性的使用,這里我們使用兩種方式進行對比解析

最最最原始的使用方式

(1)、自定義文件如下:

我們可以在TestAttrsView方法的參數AttributeSet是個xml解析工具類,幫助我們從布局的xml里提取屬性名和屬性值。

(2)、在布局文件xml中的使用

這里使用自定義屬性需要聲明xml的命名空間,其中app是命名空間,用來加在自定義屬性前面。

xmlns:app=" http://schemas.android.com/apk/res-auto "
聲明xml命名空間,xmlns意思為「xml namespace」.冒號後面是給這個引用起的別名。
schemas是xml文檔的兩種約束文件其中的一種,規定了xml中有哪些元素(標簽)、
元素有哪些屬性及各元素的關系,當然從面向對象的角度理解schemas文件可以
認為它是被約束的xml文檔的「類」或稱為「模板」。

(3)、將屬性名與屬性值列印結果如下:

從列印結果我們可以看出,AttributeSet將布局文件xml下的屬性全部列印出來了,細心的童鞋可能已經看出來:

這個屬性我們設置的是一個整數尺寸,可最後列印出來的是資源編號。

那如果我們想要輸出我們設置的整數尺寸,需要怎麼操作呢?

這個時候就該我們這篇的主角出場了,使用TypedArray方式。

(1)、這里我們需要將attrs.xml使用「declare-styleable」標簽進行改造,如下:

從改造後的attrs文件可以看出,我們將屬性聲明歸結到TestStyleable裡面,也就意味著這些屬性是屬於TestStyleable下的。

(2)、屬性的解析:

這里我直接列印出解析結果,這里可以獲取我們想要的自定義屬性,而系統有的屬性可以忽略。

(3)、運行結果如下

從解析的結果可以看出,尺寸的結果已經轉換為實際值了:

這個時候有童鞋又問了,我設置的是15dp,為啥最後列印是41.25了呢?其實解析出來的值單位是px,所以這里輸出的是轉換後的值。

解析的過程中用到了這個方法:

我們來看一下這個方法的源碼:

源碼中我們可以看到這個方法有兩個參數:

obtainStyledAttributes方法返回值類型為TypedArray。該類型記錄了獲取到的屬性值集合,而通過數組下標索引即可找到對應的屬性值。索引下標通過R.styleable.TestStyleable_xx獲取,"xx"表示屬性名,一般命名為"styleable名" + "_" + "屬性名"。

而TypedArray提供了各種Api,如getInteger,getString,getDimension等方法來獲取屬性值,這些方法都需要傳入對應屬性名在obtainStyledAttributes中的int數組的位置索引,通過下標獲取數組里屬性值。

這個TypedArray的作用就是資源的映射作用,把自定義屬性在xml設置值映射到class,這樣怎麼獲取都很簡單啦。

到這里就分析完啦!

熱點內容
手機號序列碼的密碼在哪裡 發布:2025-02-02 03:29:34 瀏覽:873
安卓怎麼換回鴻蒙系統 發布:2025-02-02 03:24:35 瀏覽:507
完美國際鄰水鎮箱子密碼是多少 發布:2025-02-02 03:17:04 瀏覽:618
測試java程序 發布:2025-02-02 03:16:49 瀏覽:888
android羅升陽 發布:2025-02-02 03:15:01 瀏覽:823
javascript編程語言 發布:2025-02-02 03:05:49 瀏覽:361
用電賬號初始密碼多少 發布:2025-02-02 03:04:03 瀏覽:108
python賦值運算符 發布:2025-02-02 03:00:51 瀏覽:905
怎麼查詢電腦ip地址和dns伺服器 發布:2025-02-02 02:57:50 瀏覽:240
資料庫應用系統的概念 發布:2025-02-02 02:44:46 瀏覽:549