安卓基礎雜七雜八
文章目錄
1. xml改TextView樣式
(1) 動態改變字體顏色、背景可以直接用drawable裏面的xml來寫
layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:text="hello world"
android:textSize="20sp"
android:textColor="@drawable/change_text_color"
android:background="@drawable/change_text_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
drawable/change_text_color.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/white" android:state_selected="true" />
<item android:color="@android:color/black" />
</selector>
drawable/change_text_background.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@mipmap/aaa" android:state_selected="true" />
<item android:drawable="@mipmap/bbb" />
</selector>
(2) 動態改變TextView的字體大小、字體樣式、位置等非常多屬性的時候可以在styles.xml裏面寫
com/example/pack/MainActivity.java
TextView textView = (TextView) findViewBy(R.id.textView);
textView.setTextAppearance(this, textView.isSelected() ? R.style.text_view_selected : R.style.text_view_not_selected);
values/styles.xml
<resources>
<style name="text_view_selected">
<item name="android:textSize">25sp</item>
<item name="android:textStyle">bold</item>
</style>
<style name="text_view_not_selected">
<item name="android:textSize">23sp</item>
<item name="android:textStyle">normal</item>
</style>
</resources>
(3) TextView的文本內容動態添加下劃線效果可以用xml來做
layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:text="hello world"
android:textSize="20sp"
android:textColor="@android:color/black"
android:background="@drawable/text_underlined"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
drawable/text_underlined.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<layer-list>
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<corners android:radius="0dp" />
</shape>
</item>
<!-- 在當前的背景圖上再加一個小一點細一點的長方形 然後調整位置 貼在最下面 -->
<item android:gravity="bottom|center" android:left="8dp" android:top="40dp" android:right="8dp">
<shape android:shape="rectangle">
<solid android:color="@android:color/black" />
<corners android:radius="0dp" />
</shape>
</item>
</layer-list>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<corners android:radius="0dp" />
</shape>
</item>
</selector>
(4) TextView做成像button一樣增加點擊效果的陰影可以用xml實現
layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:text="hello world"
android:textSize="20sp"
android:textColor="@android:color/black"
android:background="@drawable/change_text_background_press"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
drawable/change_text_background_press.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<layer-list>
<item android:drawable="@mipmap/aaa" />
<!-- 相當於在原來圖片的表面再覆蓋一層深色的略帶透明的圖片 -->
<item>
<shape android:shape="rectangle">
<!-- 調整透明度 -->
<solid android:color="#26000000" />
<corners android:radius="0dp" />
</shape>
</item>
</layer-list>
</item>
<item>
<item android:drawable="@mipmap/aaa" />
</item>
</selector>
(5) TextView做成像button一樣增加不可點擊的效果可以用xml實現
layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:text="hello world"
android:textSize="20sp"
android:textColor="@android:color/black"
android:background="@drawable/change_text_background_press"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
drawable/change_text_background_press.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 加一行判斷是不是enabled -->
<!-- 因爲讀xml的時候是從上往下匹配的 所以如果enabled寫在後面不會有效果 因爲會先判斷selected -->
<item android:enabled="false">
<layer-list>
<item android:drawable="@mipmap/aaa" />
<item>
<shape android:shape="rectangle">
<solid android:color="#C2C2C2" />
<corners android:radius="0dp" />
</shape>
</item>
</layer-list>
</item>
<item android:state_selected="true">
<layer-list>
<item android:drawable="@mipmap/aaa" />
<item>
<shape android:shape="rectangle">
<!-- 調整透明度 -->
<solid android:color="#26000000" />
<corners android:radius="0dp" />
</shape>
</item>
</layer-list>
</item>
<item>
<item android:drawable="@mipmap/aaa" />
</item>
</selector>
2. FragmentPagerAdapter + ViewPager
(1) FragmentPagerAdapter
按照網上的說法,谷歌推薦用newInstance()來創建一個Fragment。
因爲當沒有強制屏幕的方向的時候,一旦屏幕發生變化,Activity會重新加載,相應地Fragment也會重新加載。
在Fragment重新加載的過程中,如果沒有用newInstance()來創建,那麼之前傳進來的Bundle可能會丟失。
參考:【Android面試】爲什麼要用newInstance來實例化Fragment
com/example/pack/fragments/TestFragment.java
public class TestFragment extends Fragment {
int arg1;
String arg2;
public static TestFragment newInstance(int param1, String param2) {
TestFragment fragment = new TestFragment();
Bundle data = new Bundle();
data.putInt("param1", param1);
data.putString("param2", param2);
fragment.setArguments(data);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle data = getArguments();
if (data != null) {
arg1 = data.getInt("param1");
arg2 = data.getInt("param2");
}
}
// 如果在新建Fragment的時候選了自動生成回調函數的話
// 那麼記得實現它 否則會一直報錯
// 如果不想實現它 那麼記得要刪掉它
}
com/example/pack/adapters/TestFragmentAdapter.java
public class TestFragmentAdapter extends FragmentPagerAdapter {
// 不需要額外寫一個Fragment數組來緩存
// 因爲原來的FragmentPagerAdapter提供了緩存機制
// 源碼裏面有一個public Object InstantiateItem(ViewGroup container, int position) {}
// FragmentPagerAdapter不會每一次都調用getItem()
// 因爲它調用過一次getItem之後就會緩存fragment 並且給一個id給這個fragment
// 下一次需要調用的時候 先去緩存裏面找 如果沒有才重新getItem()
// private Fragment[] list;
pubic TestFragmentAdapter(FragmentManager manager) {
super(manager);
}
@Override
public long getItemId(int position) {return position;}
// 這裏的返回值只能是Fragment 不能改
// 所以自動轉換了類型
// 如果傳入一個List<Fragment> list也可以
// 但是千萬不要在塞進list的時候強制類型轉換 因爲會報錯
// 直接在newInstance的時候就要轉換
@Override
public Fragment getItem(int position) {
switch(position) {
case 0:
return TestFragment.newInstance(0);
case 1:
return TestFragment.newInstance(1);
case 2:
return TestFragment.newInstance(2);
default:
return null;
}
}
@Override
public int getCount() {return COUNT;}
}
com/example/pack/MainActivity.java
TestFragmentAdapter testFragmentAdapter = new TestFragmentAdapter(getFragmentManager());
ViewPager viewPager = (ViewPager) findViewBy(R.id.viewPager);
viewPager.setAdapter(testFragmentAdapter);
(2) ViewPager的頁面切換
如果只是純粹滾動到某一個頁面,而不需要關注像素值、偏移值或者ViewPager的頁面狀態的話可以直接重寫onPageSelected。
com/example/pack/MainActivity.java
TestFragmentAdapter testFragmentAdapter = new TestFragmentAdapter(getFragmentManager());
ViewPager viewPager = (ViewPager) findViewBy(R.id.viewPager);
viewPager.setAdapter(testFragmentAdapter);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
@Override
public void onPageSelected(int position) {
// 當點擊到某個按鈕時 需要ViewPager跳轉到特定頁面
// 源碼提供的setCurrentItem有兩個
// 一個是setCurrentItem(int item) 另一個是setCurrentItem(int item, boolean smoothScroll)
// smoothScroll爲真是平滑滑動 而smoothScroll爲假就是立刻滑動到特定的頁面
// 如果不設置smoothScroll的話 也有平滑滑動 但前提是已經經過了第一層佈局
viewPager.setCurrentItem(position, SMOOTH_SCROLL);
}
@Override
public void onPageScrolledStateChanged(int state) {}
});
3. ListView、RecyclerView
(1) ListView
如果要實現的東西是打豎單排放置的列表,那就直接用ListView。
因爲RecyclerView沒有分割線,所以如果要添加分割線的話,就得寫一個ItemDecoration的類。
如果要取消ListView的分割線,可以用divider="@null
。
如果要添加最上面的,也就是頭部的分割線,可以用headerDividersEnabled
,底部的分割線可以用footerDividersEnabled
。也可以在java裏面用addFooterView()
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/ListView"
android:divider="@null"
android:footerDividersEnabled="true"
android:headerDividersEnabled="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
(2) RecyclerView
RecyclerView可以實現GridView的功能,還可以實現瀑布流,橫向縱向列表,多行的網格。
i. LayoutManager
不管是用RecyclerView實現哪種形式的列表,都要設置layoutManager,否則RecyclerView會加載不出來。
com/example/pack/MainActivity.java
RecyclerView recyclerView = (RecyclerView) findViewBy(R.id.recyclerView);
// 如果要實現GridView 那麼就要設置列數
GridLayoutManager gridLayoutManager = new GridLayoutManager(MainActivity.this, COLUMN_NUM);
recyclerView.setLayoutManager(gridLayoutManager);
// 如果要實現橫向的ListView 那麼就設置方向
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(MainActivity.this, LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(linearLayoutManager);
如果要禁止RecyclerView滾動,可以在layoutManager裏面設置,重寫canScrollHorizontally和canScrollVertically
RecyclerView recyclerView = (RecyclerView) findViewBy(R.id.recyclerView);
// 如果要實現GridView 那麼就要設置列數
GridLayoutManager gridLayoutManager = new GridLayoutManager(MainActivity.this, COLUMN_NUM, false) {
@Override
public boolean canScrollHorizontally() {return false;}
@Override
public boolean canScrollVertically() {return false;}
};
recyclerView.setLayoutManager(gridLayoutManager);
// 如果要實現橫向的ListView 那麼就設置方向
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(MainActivity.this, LinearLayoutManager.HORIZONTAL, false) {
@Override
public boolean canScrollHorizontally() {return false;}
};
recyclerView.setLayoutManager(linearLayoutManager);
ii. RecyclerView.Adapter<RecyclerView.ViewHolder>
RecyclerView的adapter是用自帶的adapter,也就是要提前定義好ViewHolder。這個adapter的ViewHolder其實和BaseAdapter差不多,只不過是把ViewHolder單獨拿出來定義好。這個adapter和BaseAdapter最大的不同是把getView拆成了onCreateViewHolder和onBindViewHolder,就不用像BaseAdapter那樣在getView裏面寫一段if (convertView == null)
來分情況了。
placeHolder其實就是把RecyclerView的item所包含的需要改動的所有東西取出來,不需要改動的東西不用取出來,例如如果每一個item都要插入一個一模一樣的icon,那這個ImageView就不用取出來。
layout/test_fragment_recycler_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:text="hello world"
android:textSize="20sp"
android:textColor="@drawable/change_text_color"
android:background="@drawable/change_text_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
com/example/pack/view/TestFragmentViewHolder.java
public class TestFragmentViewHolder extends RecyclerView.ViewHolder {
public TextView textView;
public TestFragmentViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewBy(R.id.textView);
}
}
com/example/pack/adapters/TestFragmentAdapter.java
public class TestFragmentAdapter extends RecyclerView.Adapter<TestFragmentViewHolder> {
private Context mContext;
private List<Integer> mListItems;
private int mLastNum;
public TestFragmentAdapter(Context mContext) {
this.mContext = mContext;
mListItems = new ArrayList<>();
mLastNum = 0;
}
public void setListItems(List<Integer> list) {
if (list == null) return;
if (mListItems == null) mListItems = new ArrayList<>();
else if (!mListItems.isEmpty()) mListItems.clear();
mListItems.addAll(list);
}
@Override
public int getItemCount() {return mListItems.size();}
@Override
public long getItemId(int position) {return mListItems.get(position);}
@Override
public TestFragmentViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.test_fragment_recycler_view, null);
return new TestFragmentViewHolder(view);
}
@Override
public void onBindViewHolder(TestFragmentViewHolder holder, int position) {
// 處理數據
// 各種賦值操作、回調或者別的
// 如果想默認選中第一個位置的話可以用
// 每一次刷新的時候改變lastNum就行了
holder.itemView.setSelected(mLastNum == position);
holder.itemView.setOnClickListener(new View.OnClickListener() {
});
}
public void setLastNum(int lastNum) {
mLastNum = lastNum;
}
}
com/example/pack/MainActivity.java
RecyclerView recyclerView = (RecyclerView) findViewBy(R.id.recyclerView);
TestFragmentAdapter testFragmentAdapter = new TestFragmentAdapter(MainActivity.this);
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(i);
}
testFragmentAdapter.setListItems(list);
recyclerView.setAdapter(testFragmentAdapter);
iii. 滾動到指定位置,找到指定位置的item最好用現成的函數,否則很容易出現空對象引用
com/example/pack/MainActivity.java
RecyclerView recyclerView = (RecyclerView) findViewBy(R.id.recyclerView);
// 儘量不用getChildAt,因爲findViewHolderForLayoutPosition是實現好的
recyclerView.findViewHolderForLayoutPosition(NUM).itemView.setSelected(SELECTED);
// 滾動到某個位置,也是儘量用現成的,不用重新計算滑動距離
recyclerView.scrollToPosition(POSITION);
4. findFragmentByTag和FragmentTransaction.commit
直接使用findFragmentByTag很容易出現空對象引用,需要在前面加幾行代碼
com/example/pack/MainActivity.java
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTranscation();
transaction.replace(R.id.container, new TestFragment(), "testFragment");
// 這句也要寫!!!!!!!!!!!!
transaction.addToBackStack("testFragment");
// commit不是馬上提交的,是等到後來才把FragmentTransaction提交上去的,存在延遲
// 所以當後面直接取fragment的時候可能取不到
// 一個FragmentTransaction只能提交一個commit,如果有多個任務,要不分開寫,要不最後一併提交
transaction.commit();
// 一定要寫這一句!!!!!!!!!!!
manager.executePendingTransactions();
TestFragment fragment = (TestFragment) manager.findFragmentByTag("testFragment");
5. px和dp
爲了讓UI能夠更好地適配不同分辨率的機器,肯定是用dp。因爲dp可以根據不同分辨率,在原有的UI的基礎上系統自動對其進行放大縮小。而px則是限定了位置,不會根據分辨率進行變化,很可能出現UI界面混亂的情況。
在java代碼中,尤其是跟位置有關的變量,儘量不直接寫數據去賦值,而是改用dimens.xml的數據來賦值,因爲直接賦值就是px了,不能適配。如果是改變字體的大小的話,setTextSize(int sp)
的輸入就是sp,可以直接賦值。
將dp變成px寫入代碼
values/dimens.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="dp_125">125dp</dimen>
</resources>
com/example/pack/MainActivity.java
// dp轉成像素
int px = getResources().getDimensionPixelSize(R.dimen.dp_125);
// dp轉成sp
int px = getResources().getDimensionPixelSize(R.dimen.dp_25);
TextView textView = (TextView) findViewBy(R.id.textView);
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, px);
6. TextView的滾動
可以不用ScrollView,因爲ScrollView等同於在外面再加一層佈局,不好。可以直接用TextView.setMovementMethod(new ScrollingMovementMethod()),就可以讓文本內容滾動。
ScrollingMovementMethod繼承的是BaseMovementMethod,BaseMovementMethod的源碼裏給出了向上下左右滾動的算法。
BaseMovementMethod.java
/**
* Performs a scroll left action.
* Scrolls left by the specified number of characters.
*
* @param widget The text view.
* @param buffer The text buffer.
* @param amount The number of characters to scroll by. Must be at least 1.
* @return True if the event was handled.
* @hide
*/
protected boolean scrollLeft(TextView widget, Spannable buffer, int amount) {
final int minScrollX = getScrollBoundsLeft(widget);
int scrollX = widget.getScrollX();
if (scrollX > minScrollX) {
scrollX = Math.max(scrollX - getCharacterWidth(widget) * amount, minScrollX);
widget.scrollTo(scrollX, widget.getScrollY());
return true;
}
return false;
}
/**
* Performs a scroll right action.
* Scrolls right by the specified number of characters.
*
* @param widget The text view.
* @param buffer The text buffer.
* @param amount The number of characters to scroll by. Must be at least 1.
* @return True if the event was handled.
* @hide
*/
protected boolean scrollRight(TextView widget, Spannable buffer, int amount) {
final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget);
int scrollX = widget.getScrollX();
if (scrollX < maxScrollX) {
scrollX = Math.min(scrollX + getCharacterWidth(widget) * amount, maxScrollX);
widget.scrollTo(scrollX, widget.getScrollY());
return true;
}
return false;
}
/**
* Performs a scroll up action.
* Scrolls up by the specified number of lines.
*
* @param widget The text view.
* @param buffer The text buffer.
* @param amount The number of lines to scroll by. Must be at least 1.
* @return True if the event was handled.
* @hide
*/
protected boolean scrollUp(TextView widget, Spannable buffer, int amount) {
final Layout layout = widget.getLayout();
final int top = widget.getScrollY();
int topLine = layout.getLineForVertical(top);
if (layout.getLineTop(topLine) == top) {
// If the top line is partially visible, bring it all the way
// into view; otherwise, bring the previous line into view.
topLine -= 1;
}
if (topLine >= 0) {
topLine = Math.max(topLine - amount + 1, 0);
Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine));
return true;
}
return false;
}
/**
* Performs a scroll down action.
* Scrolls down by the specified number of lines.
*
* @param widget The text view.
* @param buffer The text buffer.
* @param amount The number of lines to scroll by. Must be at least 1.
* @return True if the event was handled.
* @hide
*/
protected boolean scrollDown(TextView widget, Spannable buffer, int amount) {
final Layout layout = widget.getLayout();
final int innerHeight = getInnerHeight(widget);
final int bottom = widget.getScrollY() + innerHeight;
int bottomLine = layout.getLineForVertical(bottom);
if (layout.getLineTop(bottomLine + 1) < bottom + 1) {
// Less than a pixel of this line is out of view,
// so we must have tried to make it entirely in view
// and now want the next line to be in view instead.
bottomLine += 1;
}
final int limit = layout.getLineCount() - 1;
if (bottomLine <= limit) {
bottomLine = Math.min(bottomLine + amount - 1, limit);
Touch.scrollTo(widget, layout, widget.getScrollX(),
layout.getLineTop(bottomLine + 1) - innerHeight);
return true;
}
return false;
}
7. 自定義TextView
(1) 重寫觸摸事件
如果在某種情況下使用系統自帶的觸摸事件,某種情況下使用自己重寫的觸摸事件的時候,就要用到super.onTouchEvent(MotionEvent event)了。如果全程都用自己重寫的觸摸事件的話,那麼可以直接屏蔽到系統的觸摸事件。
MyTextView.java
// Android Studio建議加上這一行代碼
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
// 如果這種情況下需要用到原來的觸摸事件
// 那麼就直接返回父類實現好的觸摸事件
// 返回別的true或者false都是沒有用的
// 因爲已經把父類的觸摸事件屏蔽掉了
if (!mCanTouch) {
return super.onTouchEvent(event);
} else {
// 後面就是實現自己的觸摸事件了
// switch (event.getAction()) {
// case ACTION_DOWN:
// XXX;
// break;
//
// case ACTION_MOVE:
// XXX;
// break;
//
// case ACTION_UP:
// XXX;
// break;
// }
// ...
// if (xxx) return true;
// else return false;
// invalidate()
// 如果調用重繪的話,就會自動調用onDraw()
// 如果每一次觸摸都調用一次invalidate,那麼每一次都會清屏重繪
}
}
(2) Layout
用getLayout()獲取Layout一般都會存在延時的問題,可以加上一個ViewTreeObserver
MyTextView.java
private Layout mLayout;
private ViewTreeObserver mVto;
mVto = getViewTreeObserver();
mVto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void OnGlobalLayout() {
mLayout = getLayout();
}
});
如果Layout不急着用但是可以確定肯定要執行完的話,甚至可以開一個Runnable去解決
MyTextView.java
// 前面還可以加個計時器
// new CountDownTimer(MILLISECONDS, TIME_INTERVAL) {
// @Override
// public void onTick(long millisUntilFinished) {
// }
//
// @Override
// public void onFinish() {
// Log.d(TAG, "onFinish: finishCountDownTimer!");
// ...
// ...
// }
// }.start();
new Thread(new Runnable() {
@Override
public void run() {
mLayout = getLayout();
while (mLayout == null) {
mLayout = getLayout();
}
// 執行要執行的操作
// ...
// ...
}
}).start();
(3) 計算字符的位置
關於字體的top、ascent、baseline…
參考:Android font, 字體全攻略;
雖然算出了字符的座標位置,但是在後續使用的時候還要考慮到paddingTop、paddingBottom、paddingLeft、paddingRight和scrollX、scrollY。因爲前面也說了會使用到setMovementMethod,一旦滾動,位置就會發生變化。
MyTextView.java
// 獲得離(x, y)最近的字符是第幾個
int currPos = getOffsetForPosition(x, y);
Layout layout = getLayout();
// 給一整行的文字畫一個矩形框住
Rect bound = new Rect();
// 當前是第幾行
int currLine = layout.getLineForOffset(position);
// 獲取矩形的邊界
// 矩形的上邊界就是字體的上邊
// 矩形的下邊界就是字體的下邊
layout.getLineBounds(currLine, bound);
int top = bound.top;
int bottom = bound.bottom;
// 字符的左手邊的座標
int left = layout.getPrimaryHorizontal(position);
(4) 調整字符串的樣式
除了獲取子串substring這種常規的操作以外,去除字符串頭尾空格可以用現成的trim()。
MyTextView.java
String str = " xxxx ";
str.trim();
如果是要更改TextView某一部分子串的樣式,那麼就可以用上SpannableString。
參考:安卓開發中SpannableString之富文本顯示效果
8. 控制欄和軟鍵盤
(1) 不顯示控制欄
com/example/pack/MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 就是這兩行
getSupportActionBar().hide();
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
}
(2) 隱藏軟鍵盤
com/example/pack/MainActivity.java
InputMethodManger manager = (InputMethodManger) MainActivity.this.getSystemService(Context.INPUT_METHOD_SERVICE);
// 一定要判斷空不空
// 兩個都要判斷
// 不然很容易出錯
if (manager != null && MainActivity.this.getCurrentFocus() != null) manager.hideSoftInputFromWindow(MainActivity.this.getCurrentFocus().getWindowToken(), InputMethodManger.HIDE_NOT_ALWAYS);
9. Activity把數據傳回Fragment
其中一個方法可以用setResult(),也可以用廣播,不過廣播麻煩一點。
com/example/pack/fragments/TestFragment.java
private void inflateActivity() {
Bundle data = new Bundle();
data.putSerializable("dataset", dataset);
Intent intent = new Intent(getActivity(), SecondActity.class);
intent.putExtras(data);
// 因爲要接收SecondActivity傳回來的數據
// 所以要發送一個請求碼
// 是用來識別傳回來的數據是不是屬於要接收的那個Activity的數據
startActivityForResult(intent, 0x11);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 0x11) {
if (resultCode == RESULT_OK) {
String returnData = data.getStringExtra("dataString");
// 拿到數據的後續處理
// ...
// ...
}
}
}
com/example/pack/SecondActivity.java
// 跳轉到SecondActiv的時候拿到數據
private void initData() {
Intent intent = getIntent();
Bundle data = intent.getExtras();
Dataset dataset = data.getSerializable("dataset");
}
// 退出的時候要返回數據給Fragment
private void handleReturn() {
Intent intent = new Intent();
// 要傳回的數據
intent.putExtra("dataString", dataString);
// 要傳回結果
setResult(RESULT_OK, intent);
SecondActivity.this.finish();
}
10. DialogFragment
如果只是普通的DialogFragment的話,可以直接在onCreateView裏面設置佈局。如果是比較複雜,且有特定的設計的彈窗,那麼可以用onCreateDialog()。
(1) 去除黑色、白色、灰色的邊框
com/example/pack/fragments/MyDialogFragment.java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 一定要在onCreate裏面寫!!!!!!!!
// 不要在onCreateView裏面寫
// 因爲沒有效果
setStyle(DialogFragment.STYLE_NO_FRAME, R.style.FullScreen);
}
(2) 設置寬高
DialogFragment在佈局裏面設置父佈局的寬高是沒有用的,不管是具體數據還是match_parent、wrap_content這一類的參數。
如果要規定某一個具體的寬高,那麼可以在代碼裏用setLayout()
。
com/example/pack/fragments/MyDialogFragment.java
private View mView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.fragment_my_dialog, container, false);
// 就是這一句
getDialog().getWindow().setLayout(LAYOUT_WIDTH, LAYOUT_HEIGHT);
return mView;
}
如果只是想設置match_parent和wrap_content的話,那麼可以設置LayoutParams。需要注意的點是,佈局裏面不要用<RelativeLayout></RelativeLayout>
,否則很容易失效,改用<LinearLayout></LinearLayout>
。
com/example/pack/fragments/MyDialogFragment.java
private View mView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.fragment_my_dialog, container, false);
WindowManager.LayoutParams params = getDialog().getWindow().getAttributes();
params.gravity = Gravity.CENTER;
params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
// 一定要寫這一句
// 否則彈窗有可能顯示不出來或者顯示出來只有很小一塊
// PopupWindow也是一個道理 也是要設置backgroundDrawable
getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
getDialog().getWindow().setAttributes(params);
return mView;
}
11. 還是TextView
(1) 監聽內容
可以用addTextChangedListener()
監聽,可以動態計算當前輸入的字數,動態獲取當前輸入的內容。
如果要實現TextView裏面最多輸入指定數目的文字,而且字數滿了之後要提示的話,可以在xml裏用maxLength
,同時在java裏用addTextChangedListener()
。如果要限制TextView裏面輸入的最大行數,可以用maxLines
。
layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:text="hello world"
android:maxLength="31"
android:maxLines="2"
android:textSize="20sp"
android:textColor="@drawable/change_text_color"
android:background="@drawable/change_text_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
com/example/pack/MainActivity.java
TextView textView = (TextView) findViewBy(R.id.textView);
textView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// count就是當前輸入字數
// 如果檢測到某一個特定的數字就要顯示彈窗
if (s.length() == MAX_LENGTH) Toast.makeText(MainActivity.this, "文字已輸滿!", Toast.LENGTH_SHORT).show();
}
});
(2) 設置走馬燈效果或者省略號效果
走馬燈效果裏面自動包含了省略號效果,當TextView獲取到焦點的時候,文字會滾動;當沒有獲取到焦點的時候,例如突然彈出了PopupWindow,文字不會滾動,並且超出部分會以省略號...
顯示。
layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:text="hello world"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:textSize="20sp"
android:textColor="@drawable/change_text_color"
android:background="@drawable/change_text_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
如果是省略號效果,可以用ellipsize="end"
,加上singleLine
就是顯示一行,如果要限制一行要顯示的字數,可以用maxEms
。那麼在maxEms
規定的字數到了以後,後面的內容以省略號顯示。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:text="hello world"
android:ellipsize="end"
android:singleLine="true"
android:maxEms="8"
android:textSize="20sp"
android:textColor="@drawable/change_text_color"
android:background="@drawable/change_text_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
(3) 設置光標的位置和樣式
有時候輸入setText以後,光標還是停在最開始的位置,這樣不行,可以用setSelection()
。
com/example/pack/MainActivity.java
TextView TextView = (TextView) findViewBy(R.id.autoCompleteTextView);
// 設置光標的位置在字符串末尾
TextView.setSelection(TextView.getText().toString().length());
改變光標的樣式,例如換成一張圖片,可以在xml裏用textCursorDrawable
。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:text="hello world"
android:cursorVisible="true"
android:texctCursorDrawable="@mipmap/cursor"
android:textSize="20sp"
android:textColor="@drawable/change_text_color"
android:background="@drawable/change_text_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
(4) 動態添加TextView
可以預先在xml裏面定義好一個<LinearLayout></LinearLayout>
,然後往裏面加TextView。
com/example/pack/MainActivity.java
LinearLayout layout = (LinearLayout) findViewBy(R.id.layout);
// 把裏面有的子組件都清空
layout.removeAllViews();
// 設置位置
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_VERTICAL;
// 儘量不要直接寫數據 而是獲取dimens.xml裏的dp
lp.setMargins(getResources().getDimensionPixelSize(R.dimen.dp_25), 0, 0, 0);
TextView textView = new TextView(MainActivity.this);
textView.setLayoutParams(lp);
textView.setTextSize(22);
// 因爲那是id值
// 不是顏色對應的那個int值
// 如果直接在setTextColor裏面寫R.drawable.change_font_color不會報錯
// 不過它只能獲取到第一種顏色
ColorStateList colorStateList = getResources().getColorStateList(R.drawable.change_font_color);
textView.setTextColor(colorStateList);
// 如果要給textView的內容添加富文本效果
// 可以用SpannableString
SpannableString content = new SpannableString(CONTENT);
content.setSpan(new UnderlineSpan(), 0, LENGTH, 0);
textView.setText(content);
// 最後記得把建好的textView加入到預先創建好的LinearLayout裏面
layout.addView(textView);
12. AutoCompleteTextView自動完成文本框
如果在輸入內容過程中,文本框可以根據輸入的內容自動下拉提示框,那麼可以用AutoCompleteTextView並且綁定一個數組。
AutoCompleteTextView和EditText的用法類似,除了多幾個屬性dropDownHeight
、dropDownVerticalOffset
……
(1) 綁定數組
可以用Arrays
加上ArrayAdapter
來綁定
參考:Android使用xml文件中的array資源
com/example/pack/MainActivity.java
AutoCompleteTextView autoCompleteTextView = (AutoCompleteTextView) findViewBy(R.id.autoCompleteTextView);
int[] arr = getResources().getArray(R.array.language);
ArrayAdapter adapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_dropdown_item_line, arr);
autoCompleteTextView.setAdapter(adapter);
如果要給AutoCompleteTextView的下拉列表設置點擊事件,可以用setOnItemClickListener()
,用法跟其他View的組件一樣。
13. 線程
如果要讀入大量數據的話,不能直接在UI線程裏直接讀取,否則會阻塞界面,可能會出現界面加載超時,activity可能會自動取消。這個時候需要用到子線程,當子線程處理完數據之後向Handler
發送消息,由Handler
控制下一步走到哪裏。
如果數據特別多,處理起來很慢,那麼可以考慮分部分加載。分部分可以考慮按時間間隔來加載,也可以按照一段一段數據來加載。
如果要用new Thread()
的話,最好是在確保這個Thread
一定會完成的情況下用,因爲Thread.interrupt()
和Thread.join()
並不是終止線程,而是暫時打斷線程,而且並不知道打斷完之後,線程運行到哪一個位置。
因爲有用上Handler
,所以Destroy
的時候也要把Handler
移除。
com/example/pack/MainActivity.java
private Handler mHandler = new Handler() {
@Override
public void dispatchMessage(Message msg) {
switch (msg.what) {
case 0x11:
// ...
break;
case 0x12:
// ...
break;
default:
break;
}
}
};
@Override
protected void onDestroy() {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
}
線程具體的用法如下:
參考:Android線程管理之ExecutorService線程池
參考:Android: How do I stop Runnable?
參考:How to cancel all the thread/ threads in ExcecutorService?
如果使用到多線程的話,記得在某些函數上加鎖synchronized
,否則會出現多個線程同時進入一個函數的情況,導致結果出錯。
14. ObjectAnimator動畫
如果只是單純想要動畫效果,而不需要對正在運動的組件加上監聽器之類的操作的話,可以直接用逐幀動畫或者補間動畫。但是如果要對組件進行操作的話,最好使用屬性動畫。因爲前兩個在組件運動後,不會更新組件的位置,也就是說如果一個button從a點運動到b點,雖然在屏幕上看到button在b點上,但是如果此時點擊b點,button不會有反應,因爲監聽器綁定的位置還是在a點。
參考:Android動畫之ObjectAnimator
參考:Android中的 ObjectAnimator 動畫
參考:< Android 基礎(三十一)> ObjectAnimator
參考:Android 屬性動畫(Property Animation) 使用詳解
參考:Android動畫之ObjectAnimator實現補間動畫和ObjectAnimator自定義屬性
參考:Android開發中屬性動畫(ObjectAnimator)中 插值器(Time Interpolator )詳解