Android UI开发细节Api使用技巧总结

收集平时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是否复用,可以在复用时取消下载图片,复用肯定就是在滑动,所以优化图片加载可以

  • 通过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的复用回收

你可能感兴趣的:(Android,UI开发)