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 )详解

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