note_26:安卓基礎雜七雜八

安卓基礎雜七雜八



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的用法類似,除了多幾個屬性dropDownHeightdropDownVerticalOffset……

(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 )詳解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章