收集平时UI开发中使用到或者学习笔记做个收录,好记性不如烂笔头:持续更新中。。。。
1、图片渐变技巧
第一种:叠层退去,逐渐显示底层
第二种:直接组合使用,各取一部分组成一个,利用canvas.clipRect来截取
第三种:叠层慢慢增加并且和底层取个交集:取两层绘制交集,显示上层。
PorterDuffXfermode mode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
mPaint.setXfermode(mode);
类似的画圆角也可以用这种,图层叠加模式,画圆角还可以用shader,paint.setShader(BitmapShader)填充到具体形状中,如roundRect circle中
2、StaticLayout
android中处理文字换行的一个工具类,StaticLayout已经实现了文本绘制换行处理。其实TextView也是调用StaticLayout来实现换行的。
3、drawable.setLevel 和imageView.setImageLevel
在用来显示drawable进程的时候的 根据level值来作为进度变化,设置之后 drawable会收到onLevelChange回调通知进度变化
4、View控件的移动位置方式总结
1)改变layoutParams的with height 不建议
2)重新layout,计算位置调用view.layout() 不建议
3)调用offsetLeftAndRight(offX)和offsetTopAndBottom(offY); 建议
4)setTranslationX和setTranslationY 可以
这个也可以改变view的位置,与上面的区别是,不影响改变layoutParams中margin大小 即不改变getLeft和getRight大小,translationX就是针对getLeft大小而言的偏移量,也是一种设置与父布局大小的方式和Margin同级,但是坐标位置都是改变的,即getX =getLeft+getTranslationX
public float getX() {
return mLeft + getTranslationX();
}
上述4种均可以改变位置和点击属性
这个和移动父布局中的控件不一样,和scrollTo、scrollBy是移动ViewGroup内容,和canvas.tanslate移动一样,都是画布内容在可视布局区域内的移动,不会超过边界。而View动画-TranslationAnimation,是改变Matrix变换来是实现可视位置位移,但不改变控件的属性,如点击监听位置。
5、View的尺寸计算
1)View.post(Runnable):把计算任务post主线程队列中handlelauchActivity之后,即在界面显示之后,why?
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
把Runnable任务通过handle传递到主线程之后,而此时onCreate正是发生在主线程消息队列LaunchActivity 的Message之后,即在执行完这个Message之后,UI界面已经绘制测算完成,这时候再执行Runnable这个Message既可以获得尺寸
2)onWindowFocusChange:同上,都是在完整生命周期之后,handleResumeActivity已经执行完成了
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
// TODO Push resumeArgs into the activity for consideration
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);
final int forwardBit = isForward ?
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);//完成测算
}
.................................
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();//显示界面
}
}
if (!r.onlyLocalRequest) {
r.nextIdle = mNewActivities;
mNewActivities = r;
if (localLOGV) Slog.v(
TAG, "Scheduling idle handler for " + r);
Looper.myQueue().addIdleHandler(new Idler());
}
r.onlyLocalRequest = false;
// Tell the activity manager we have resumed.
if (reallyResume) {
try {
ActivityManagerNative.getDefault().activityResumed(token);
} catch (RemoteException ex) {
}
}
} else {
// If an exception was thrown when trying to resume, then
// just end this activity.
try {
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null, false);
} catch (RemoteException ex) {
}
}
}
在addView之后ViewRootImpl会完成view的measure、layout、draw,故可以获取,而onRusmue是发生在performRusumeActivity内,在addView之前。
3 ) 、ViewTreeObserver来监听绘制、布局等事件变化
4)IdleHandler 使用,当Activity启动时候,如果MessageQueue中的message执行完毕了,此时Ui绘制也完成了,idleHandler会得到执行。不会阻塞Ui的同时,肯定再Ui Message之后执行。
6、自定义View相关技巧和基础
两篇不错的文章收集
安卓自定义View教程目录:http://www.gcssloop.com/customview/CustomViewIndex/
Android应用自定义View绘制方法手册 :http://blog.csdn.net/yanbober/article/details/50577855
7、之前写过Android图片自动适配的文章,如果有些图片我们不希望手机进行放大适配更高的分辨率怎么办?根据《Sdk界面UI开发自动适配屏幕技巧》所说,通过density的变化来确定,那么我们指定inDensity跟TargetDensity一样大就可以了,在Resource中确实存在这样的方法:
public Drawable getDrawableForDensity(@DrawableRes int id, int density)
throws NotFoundException {
return getDrawableForDensity(id, density, null);
}
这个入参density的赋值路径:
if (value.density > 0 && value.density != TypedValue.DENSITY_NONE) {
if (value.density == density) {
value.density = metrics.densityDpi;
} else {
value.density = (value.density * metrics.densityDpi) / density;
}
}
先记录下,后面验证。getDrawableForDensity(id,getResources().getDisplayMetrics().densityDpi);因为inTargetDensity = res.getDisplayMetrics().densityDpi;两个值一样即可
8、RecyclerView.Adapter监听viewHolder是否复用,可以在复用时取消下载图片,复用肯定就是在滑动,所以优化图片加载可以
@Override
public void onBindViewHolder(Myholder holder, int position) {
String istrurl = mImgList.get(position).getImageUrl();
if (null == holder || null == istrurl || istrurl.equals("")) {
return;
}
Glide.with(mContext)
.load(istrurl)
.placeholder(R.drawable.ic_launcher_background)
.into(holder.mImvShow);
}
@Override
public void onViewRecycled(Myholder holder)
{
if (holder != null)
{
Glide.clear(holder.mImvShow);
}
super.onViewRecycled(holder);
}
9.RecyclerView 局部刷选 item内部单个控件而非整个item刷选
notifyItemChanged(position,payloads)
源码
public final void notifyItemChanged(int position, Object payload) {
mObservable.notifyItemRangeChanged(position, 1, payload);
}
public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
}
而notifyItemChanged(position)
public final void notifyItemChanged(int position) {
mObservable.notifyItemRangeChanged(position, 1);
}
mObservable中的notifyItemRangeChanged方法
public void notifyItemRangeChanged(int positionStart, int itemCount) {
notifyItemRangeChanged(positionStart, itemCount, null);//payload为null 进行整个item的刷新
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
//为空 不使用
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position,List payloads) {
final ContactHolder contact= (ContactHolder) holder;
if(payloads.isEmpty()){//payloads为空 即不是调用notifyItemChanged(position,payloads)方法执行的
//在这里进行初始化item全部控件
contact.userName.setText(mList.get(position).getName());
contact.userId.setText(mList.get(position).getId());
contact.userImg.setImageResources(mList.get(position).getImg());
}else{//payloads不为空 即调用notifyItemChanged(position,payloads)方法后执行的
//在这里可以获取payloads中的数据 进行局部刷新
//假设是int类型
int type= (int) payloads.get(0);// 刷新哪个部分 标志位
switch(type){
case 0:
contact.userName.setText(mList.get(position).getName());//只刷新userName
break;
case 1:
contact.userId.setText(mList.get(position).getId());//只刷新userId
break;
case 2:
contact.userImg.setImageResources(mList.get(position).getImg());//只刷新userImg
break;
}
}
}
10、沉浸式title
statusBar 背景设置以及状态图片和字体颜色控制
"android:windowLightStatusBar"为true 会变成灰色字体和图标
“android:statusBarColor”:statusBar背景颜色 默认会取colorPrimaryDark颜色值
11、自定义styleable
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CommonTitleBar);
a.getColor()值本身就是负直,不要去做判断是否大于0来设置setColor
12、RecyclerView 中获取itemView
//当前显示出来的view,RecyclerView的全部子view中
ViewHolder viewHolder = recyclerView.findViewHolderForLayoutPosition(position);
//从recycler中scrap、 mcacheView、 recyclerViewPool缓存中获取
try {
Field mRecyclerField = content.getClass().getDeclaredField("mRecycler");
mRecyclerField.setAccessible(true);
Recycler recycler = (Recycler)mRecyclerField.get(content);
return recycler.getViewForPosition(position);
} catch (Exception var5) {
var5.printStackTrace();
return null;
}
13、View截屏/图方式
1、VIew的buildCache
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
bitmap=view.getDrawingCache()
view.destroyDrawingCache();
view.setDrawingCacheEnabled(false);
12、canvas.draw
view.draw(canvas)
第二种方式能够把clipChildren为false的超出部分能够包括进来
13、数字格数化显示以及spanableString 动态改变
/**
* 获取带位分隔格式化的double数的字符串
*
* @param num 需要被格式化的数
* @param groupingSize 需要几位整数分隔
* @param fractionDigits 需要保留几位小数
* @param preffix 前缀字符
* @param suffix 后缀字符
* @return 格式化后的字符串
* @author chenca
*/
public static String getGroupingFormat(double num, int groupingSize, int fractionDigits, String preffix, String suffix) {
DecimalFormat df = new DecimalFormat();
df.setMaximumFractionDigits(fractionDigits);
df.setMinimumFractionDigits(fractionDigits);
df.setGroupingSize(groupingSize);
df.setPositivePrefix(preffix);
df.setNegativePrefix(preffix);
df.setPositiveSuffix(suffix);
df.setNegativeSuffix(suffix);
df.setRoundingMode(RoundingMode.UP);
return df.format(num);
}
总结一下以上提到的Span
ForegroundColorSpan:前景色
BackgroundColorSpan:背景色
ClickableSpan:抽象类,可点击效果,重写onClick方法响应点击事件
URLSpan:超链接
MaskFilterSpan:EmbossMaskFilter浮雕效果,BlurMaskFilter模糊效果
RelativeSpan:文字相对大小
AbsoluteSpan:文字绝对大小
ScaleXSpan:x轴缩放
styleSpan:文字样式
TypefaceSpan:文字字体类型
TextApearanceSpan:文字外貌
UnderlineSpan:下划线
StrikeThroughSpan:删除线
SuperscriptSpan:上标
SubscriptSpan:下标
ImageSpan:图片
14、Dialog的全屏设置
1)decorView需要全屏设置,去掉背景等
跟手机有关:如去掉padding(0,0,0,0) background
2) window设置 WindowManager.LayoutParams 的width height等
3)在setOnContentView之后 设置layout 布局根View的layoutParams.width的实际全屏大小值来扩展decordView的大小
15、ScrollView和RecyclerView的滑动实现区别
前者通过canvas.translate(mScrollX,mscrollY)来改动视图绘制区域,滑动的实现是通过scrollTo来改变mScrollX和mScrollY的变量 并通知刷新重绘,invlidate,会导致重新draw
后者则是通过重新layout子view的位置实现移动,mleft,mTop,mBottom,mRight等改变,实现是通过mFling的smothScrollBy 最后还是调用view的offSetTopAndBottom来实现上下移动,移动之后并且实验view的复用回收