需求描述:
- 採用鴻洋大神打造的萬能的ListView GridView介面卡;
- ListView中的item中有一個TextView,該TextView的寬度確定,根據要顯示的內容長度動態調整文字的顯示方式:不超過1行居中顯示;超過1行的話無論第二行有幾個字,左對齊顯示。效果圖如下所示(這裡只找到了居中的示例):
剛開始寫的item佈局檔案如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fl_root"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RoundedImageView
android:id="@+id/iv_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/friendshipislandbg4"
android:riv_border_width="0dp"
android:riv_corner_radius="8dp" />
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="bottom"
android:background="@drawable/bs_school_name_bg"
android:gravity="center"
android:maxLines="2"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:textColor="#ffffff"
android:textSize="16sp" />
</FrameLayout>
複製程式碼
動態程式碼調整TextView的Gravity程式碼如下:
@Override
public void convert(ListviewViewHolder holder, TempDataBean item) {
FrameLayout flRoot = (FrameLayout) holder.getView(R.id.fl_root);
//動態調整GridView中每一個item的大小,防止圖片大小不同導致每一個item的大小不同
ViewGroup.LayoutParams params = flRoot.getLayoutParams();
params.height = displayWidth / 2 - MyUtils.dip2px(ctx, 18);
params.width = displayWidth / 2 - MyUtils.dip2px(ctx, 18);
flRoot.setLayoutParams(params);
flRoot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(BSItemActivity.this, BSDetailActivity.class));
}
});
//顯示圖片
mImageLoader.displayImage(item.getSchoolImage(), (ImageView) holder.getView(R.id.iv_school_image), options, mImageLoadingListener);
//用來判斷是學校名字是一行或者兩行
holder.setText(R.id.tv_name, item.getName());
TextView tv = (TextView) holder.getView(R.id.tv_name);
int lineCount = tv.getLineCount();
if(lineCount<=1){
// 1行居中
tv.setGravity(Gravity.CENTER);
}else{
// 2行居左
tv.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
}
}
複製程式碼
編譯,安裝,跑起來,發現並不是預期的效果。現在的效果是第一次進入頁面,未滑動,無論是一行還是兩行都是居中顯示。將第一屏滑出介面,並再次滑入介面的時候,1行的居中,2行的居左(符合預期)。我擦,這是什麼情況。帶著疑問,我將懷疑的目光轉向了大神造的輪子。
public static ListviewViewHolder get(Context context, View convertView, ViewGroup parent, int layoutId, int position) {
if (convertView == null) {
View itemView = LayoutInflater.from(context).inflate(layoutId, parent,false);
ListviewViewHolder holder = new ListviewViewHolder(context, itemView, parent, position);
holder.mLayoutId = layoutId;
return holder;
} else {
ListviewViewHolder holder = (ListviewViewHolder) convertView.getTag();
holder.mPosttion = position;
return holder;
}
}
複製程式碼
我懷疑這段程式碼是不是少了一些東西,大神沒有考慮到可能會有動態修改佈局的操作,而僅僅在public void convert(ListviewViewHolder holder, TempDataBean item)
方法中考慮了資料替換的操作。
我又懷疑是不是使用LayoutInflater
填充出來的View和複用的convertView有一些不同。難道應該在使用LayoutInflater
進行填充View的時候就進行動態修改佈局的操作,對複用的convertView修改佈局引數莫非晚了?說幹就幹,我就寫了一個抽象類,在使用LayoutInflater
填充出View之後立即對View進行修改佈局操作。抽象類如下:
public abstract class SetLayoutProperty {
View itemView;
int position;
//獲得填充出來的View及對應的位置
public void setItemView(View itemView, int position) {
this.itemView = itemView;
this.position = position;
}
//動態改變佈局引數的具體操作
public abstract void setLayoutProperty();
public View getItemView() {
return this.itemView;
}
public int getPosition() {
return position;
}
}
複製程式碼
然後修改了大神造的ViewHolder如下:
public static ListviewViewHolder get(Context context, View convertView, ViewGroup parent, int layoutId, int position,SetLayoutProperty setLayoutProperty) {
if (convertView == null) {
View itemView = LayoutInflater.from(context).inflate(layoutId, parent,false);
//主要增加了這個語句塊
if (setLayoutProperty != null) {
setLayoutProperty.setItemView(itemView,position);
setLayoutProperty.setLayoutProperty();
}
ListviewViewHolder holder = new ListviewViewHolder(context, itemView, parent, position);
holder.mLayoutId = layoutId;
return holder;
} else {
ListviewViewHolder holder = (ListviewViewHolder) convertView.getTag();
holder.mPosttion = position;
return holder;
}
}
複製程式碼
由程式碼可以看出,如果抽象類不為空,說明具體操作實現了這個抽象類,呼叫setLayoutProperty.setItemView(itemView,position)
方法將需要的內容傳出,呼叫setLayoutProperty.setLayoutProperty()
方法將執行具體的修改佈局引數的邏輯。
又修改了大神造的Adapter如下:
/**
* 在複用之前(展示之前)對一些佈局引數進行調整
*
* @return
*/
public SetLayoutProperty setLayoutProperty() {
return null;
}
複製程式碼
使用輪子的時候我選擇這樣寫:
@Override
public SetLayoutProperty setLayoutProperty() {
return new SetLayoutProperty() {
@Override
public void setLayoutProperty() {
//獲取條目佈局
View itemView = this.getItemView();
TextView tv = (TextView) itemView.findViewById(R.id.tv_school_name);
tv.setText(datas.get(getPosition()).getSchoolName());
int lineCount = tv.getLineCount();
LogSwitchUtils.logD(BSItemActivity.class,"學校名字的內容為:"+datas.get(getPosition()).getSchoolName());
LogSwitchUtils.logD(BSItemActivity.class,"學校名字的行數為:"+lineCount);
if (lineCount <= 1) {
tv.setGravity(Gravity.CENTER);
} else {
tv.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
}
}
};
}
@Override
public void convert(ListviewViewHolder holder, TempDataBean item) {...}
複製程式碼
效果是:無論是否滑出或者滑入,無論是1行還是2行,都是居中顯示。
通過列印的日誌,我發現通過getLineCount()
方法獲得的數值都為0。不應該啊,讓我看看這個方法,於是就發現:
/**
* Return the number of lines of text, or 0 if the internal Layout has not
* been built.
*/
public int getLineCount() {
return mLayout != null ? mLayout.getLineCount() : 0;
}
複製程式碼
我了個去,此時此刻我只想知道如何讓 The internal Layout has been built.
或者什麼時候The internal Layout has been built.
於是乎,我就找度娘,我給度娘說:Return the number of lines of text, or 0 if the internal Layout has not been built.
然後度娘沒有告訴我想要的答案:即如何讓?或者什麼時候?
我又接著問度娘:“getlinecount一直是0?”
度娘說了:TextView可以通過getLineCount獲取行數,但是這裡要在控制元件繪畫後才能獲取,否則呼叫這個函式返回值為0。但是我想在addText時就獲得text的行數,這樣我可以控制TextView的高度,請問有沒有一些開源的程式碼或者想法可以實現在addText獲取行數呢?
哈哈哈哈....在知乎我知道了什麼時候獲取行數不為0,程式碼如下。
final TextView totalTitleNo = (TextView) findViewById(R.id.tv_ac_sub_account);
ViewTreeObserver vto = totalTitleNo.getViewTreeObserver();
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
int lineCount = totalTitleNo.getLineCount();
System.out.println(lineCount);
}
});
複製程式碼
迫不及待,我趕緊試試:
@Override
public void convert(ListviewViewHolder holder, TempDataBean item) {
FrameLayout flRoot = (FrameLayout) holder.getView(R.id.root_fl);
ViewGroup.LayoutParams params = flRoot.getLayoutParams();
params.height = displayWidth / 2 - MyUtils.dip2px(ctx, 18);
params.width = displayWidth / 2 - MyUtils.dip2px(ctx, 18);
flRoot.setLayoutParams(params);
flRoot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(BSItemActivity.this, BSDetailActivity.class));
}
});
//顯示圖片
mImageLoader.displayImage(item.getSchoolImage(), (ImageView) holder.getView(R.id.iv_school_image), options, mImageLoadingListener);
//為測試的控制元件設定值,用來判斷是學校名字是一行或者兩行
holder.setText(R.id.tv_school_name, item.getSchoolName());
final TextView tv = (TextView) holder.getView(R.id.tv_school_name);
ViewTreeObserver vto = tv.getViewTreeObserver();
//這個監聽器看名字也知道了,就是在繪畫完成之前呼叫的,在這裡面可以獲取到行數,當然也可以獲取到寬高等資訊。
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
int lineCount = tv.getLineCount();
LogSwitchUtils.logD(BSItemActivity.class, "學校名字的行數為:" + lineCount);
if (lineCount <= 1) {
tv.setGravity(Gravity.CENTER);
//tv.invalidate();
} else {
tv.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
//tv.invalidate();
}
return true;
}
});
}
複製程式碼
一開始,我的最後一句程式碼是return false;
但發現不行,該介面顯示不出來但一直打Log,於是就選擇返回了true。
一開始,最後一個註釋其實並不是註釋,但是當我將它註釋掉,發現結果依然是正確的,它就成了註釋。
在解決問題的時候,我還問過度娘:android Textview 一行居中,兩行居左?
檢索度娘得到了如下使用佈局檔案使TextView實現一行居中,兩行居左的效果:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root_fl"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.makeramen.roundedimageview.RoundedImageView
android:id="@+id/iv_school_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/friendshipislandbg4"
app:riv_border_width="0dp"
app:riv_corner_radius="8dp" />
//該層使文字顯示在底部
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/bs_school_name_bg"
android:layout_gravity="bottom">
<LinearLayout
//該層使單行居中
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical">
<TextView
//該層使兩行居左
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:id="@+id/tv_school_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left"
android:maxLines="2"
android:textColor="#ffffff"
android:textSize="16sp" />
</LinearLayout>
</RelativeLayout>
</FrameLayout>
複製程式碼
至此,實現【Android Textview 一行居中 兩行居左】需求時我的所想,所做,所得都記錄在了這裡,便於以後複習。
結論
- 大神造的輪子沒有錯,是自己的理解有錯。但敢於向權威質疑的精神是對的,在修改輪子的時候,順便練習了使用抽象類和介面的使用;
- 實現TextView的單行居中,兩行居左方法有:動態調整佈局引數;完善佈局檔案;
- 深刻體會了
textview.getLineCount()
方法。