Android O Launcher3-Workspace加载

Android O Launcher3-Workspace加载,第1张

一简述:  

Launcher这里我们研究主要是Launcher3(Android O平台),各个手机公司自家的ROM Launcher,咱们也看不到,但是八九不离十啦,他们也应该是重写关键函数来实现他们的需要。

二流程详述:  

1核心方法:  

LauncherProvider#loadDefaultFavoritesIfNecessary() 

A第一种:从某个设定好的APK(特定packageName)里面取得xml  

这里关键实现方法为:createWorkspaceLoaderFromAppRestriction()。在这会去从UserManager获取对应的Bundle对象,当Bundle对象中包含“workspaceconfigurationpackagename”的时候,回去获取对应的apk的resouces。 

接着继续调用get()函数:1首先获取是否含有如,default_layout_6x6_h5xml(有grid size和hotseat count拼接);2然后获取是否有如,default_layout_6x6xml(有grid size拼接);3最后获取默认的default_layoutxml 

B第二种:从配置APK(与androidautoinstallsconfigactionPLAY_AUTO_INSTALL)读xml  

这里调用AutoInstallsLayout中的get函数,最关键的实现函数是findSystemApk。根据特定的action:androidautoinstallsconfigactionPLAY_AUTO_INSTALL,来获取是否system中有这发出的这个action的app,若有则去获取apk的packagename和resource。 

接着继续调用get()函数:1首先获取是否含有如,default_layout_6x6_h5xml(有grid size和hotseat count拼接);2然后获取是否有如,default_layout_6x6xml(有grid size拼接);3最后获取默认的default_layoutxml

大致流程图如下: 

C第三种:从一个preload特定名称(comandroidlauncher3actionPARTNER_CUSTOMIZATION)的APK里面取得xml  

最关键的实现函数是findSystemApk。根据特定的action:androidautoinstallsconfigactionPLAY_AUTO_INSTALL,来获取是否system中有这发出的这个action的app,若有则去获取apk的packagename和resource。 

接着通过hasDefaultLayout()来判断apk中是否有partner_default_layoutxml,若有,则将此xml作为defaultlayout

这种情况可以参看google GMS中的一个GmsSampleIntegration 应用。 

a看他的AndroidManifestxml:

b资源目录中有partner_default_layoutxml,其中有对布局的定义。

D第四种:从原生Launcher中读取xml文件(这里根据桌面dimen去选取44 55 的xml文件)  

这里主要是从InvariantDeviceProfile类中获取到对应defaultLayoutId,然后通过DefaultLayoutParser类调到用其父类AutoInstallsLayout的构造函数中进行对xml文件的解析。

获取默认的defaultLayouId主要是在InvariantDeviceProfile中获取的:在其构造函数中调用到getPredefinedDeviceProfiles(),会从device_profilesxml中选择合适的,选择的方法是获取屏幕的width和height来匹配xml文件中的minWidthDps和minHeightDps,挑选开平方后值最相近的一个profiles。最终取到对应的defaultLayouId(对应default_workspace_3x3 、default_workspace_4x4等) 

InvariantDeviceProfile的各个参数依次代表: 

配置名字(任意定义)、最小宽度(单位是dp)、最小高度(单位是dp)、桌面行数、桌面列数、文件夹行数、文件夹列数、主菜单中predicted apps最小列数、桌面Icon的size(单位是dp)、桌面Icon的文字size(单位是dp)、Hotseat的Icon个数、Hotseat的Icon的size(单位是dp)、默认的桌面配置LayoutId、demo apk的layuoutId。

大致的流程图如下: 

ps:xml文件中元素的x 、y的值最终在layout中的位置:若为正,则即为x/y;若为负,则为行/列数-y/x

首次加载的时候会走上面四种中的某一种,最终这些会被加载到db文件中,之后重启等 *** 作加载的就是直接从db中获取出来的。

三总结  

1如果有需求需要客制化的workspace,可以考虑在第四步中加入客制化的workspacexml的加载实现即可; 

2Launcher 几x几的实现即在profiles中获取的numRows和numColumns值来获取; 

3后续会专开一篇大致讲下xml的写法。

FAQ(后续补充):

自定义侧边字母导航栏,根据实际字母高度进行显示

先上效果图

public class SlideBar extends View {

    //当前手指滑动到的位置

    private int choosedPosition = -1;

    //画文字的画笔

    private Paint paint;

    //单个字母的高度

    private float perTextHeight;

    //字母的字体大小

    private float letterSize;

    //字母的垂直间距

    private float letterGap;

    //字母圆形背景半径

    private float bgRadius;

    private ArrayList<String> firstLetters = new ArrayList<>();

    //绘制点击时的蓝色背景

    private Paint backgroundPaint;

    private Context context;

    private OnTouchFirstListener listener;

    public RecyclerView getTiku_recycle_answer() {

        return tiku_recycle_answer;

    }

    public void setTiku_recycle_answer(RecyclerView tiku_recycle_answer) {

        thistiku_recycle_answer = tiku_recycle_answer;

    }

    RecyclerView tiku_recycle_answer;

    public SlideBar(Context context) {

        this(context, null);

    }

    public SlideBar(Context context, AttributeSet attrs) {

        this(context, attrs, 0);

    }

    public SlideBar(Context context, AttributeSet attrs, int defStyleAttr) {

        super(context, attrs, defStyleAttr);

        thiscontext = context;

        TypedArray typedArray = contextobtainStyledAttributes(attrs, RstyleableSlideBar);

        //字母的字体大小

        letterSize = typedArraygetDimension(RstyleableSlideBar_letter_size, DisplayUtilssp2px(context, 100f));

        //每个字母的高

        perTextHeight = typedArraygetDimension(RstyleableSlideBar_letter_height, DisplayUtilsdp2px(context, 100f));

        //字母垂直间距

        letterGap = typedArraygetDimension(RstyleableSlideBar_letter_gap, DisplayUtilsdp2px(context, 60f));

        //字母垂直间距

        bgRadius = typedArraygetDimension(RstyleableSlideBar_letter_bg_radius, DisplayUtilsdp2px(context, 80f));

        typedArrayrecycle();

        init();

    }

    public void init() {

        //初始化画笔

        paint = new Paint();

        paintsetAntiAlias(true);

        paintsetTextSize(letterSize);

        paintsetTypeface(TypefaceDEFAULT_BOLD);

        //初始化圆形背景画笔

        backgroundPaint = new Paint();

        backgroundPaintsetAntiAlias(true);

        backgroundPaintsetColor(contextgetResources()getColor(Rcolorcolor_368FFF));

    }

    public void setFirstLetters(ArrayList<String> letters) {

        firstLettersclear();

        firstLettersaddAll(letters);

        invalidate();

    }

    @Override

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int widthMode = MeasureSpecgetMode(widthMeasureSpec);  //获取宽的模式

        int heightMode = MeasureSpecgetMode(heightMeasureSpec); //获取高的模式

        int widthSize = MeasureSpecgetSize(widthMeasureSpec);  //获取宽的尺寸

        int heightSize = MeasureSpecgetSize(heightMeasureSpec); //获取高的尺寸

        int width = 0;

        int height;

        if (widthMode == MeasureSpecEXACTLY) {

            //如果match_parent或者具体的值,直接赋值

            width = widthSize;

        } else {

            //如果其他模式,则指定一个宽度

            width = DisplayUtilsdp2px(getContext(), 200f);

        }

        //高度跟宽度处理方式一样

        if (heightMode == MeasureSpecEXACTLY) {

            height = heightSize;

        } else {

            float textHeight = perTextHeight;

            height = (int) (getPaddingTop() + textHeight (firstLetterssize() + 1) + letterGap (firstLetterssize() - 1) + getPaddingBottom());

        }

        if (height > tiku_recycle_answergetMeasuredHeight()) {

            height = tiku_recycle_answergetMeasuredHeight();

        }

        //保存测量宽度和测量高度

        setMeasuredDimension(width, height);

    }

    @Override

    protected void onDraw(Canvas canvas) {

        superonDraw(canvas);

        for (int i = 0; i < firstLetterssize(); i++) {

            paintsetColor(i == choosedPosition ColorWHITE : contextgetResources()getColor(Rcolorcolor_368FFF));

            float x = (getWidth() - paintmeasureText(firstLettersget(i))) / 2;

            float y = (float) getHeight() / firstLetterssize();//每个字母的高度

            if (i == choosedPosition) {

                canvasdrawCircle((float) (getWidth() / 2), i y + y / 2, bgRadius, backgroundPaint);

            }

            //垂直位置需要增加一个偏移量,上移两个像素,因为根据计算得到的是baseline,将字母上移,使其居中在圆内

            canvasdrawText(firstLettersget(i), x, (perTextHeight + y) / 2 + y i-2, paint);

        }

    }

    //触碰事件

    //按下,松开,拖动

    @Override

    public boolean onTouchEvent(MotionEvent event) {

        switch (eventgetAction()) {

            case MotionEventACTION_DOWN:

            case MotionEventACTION_MOVE:

                thissetBackgroundColor(contextgetResources()getColor(androidRcolortransparent));

                float y = eventgetY();

                //获取触摸到字母的位置

                choosedPosition = (int) y firstLetterssize() / getHeight();

                //上滑超过边界,显示第一个

                if (choosedPosition < 0) {

                    choosedPosition = 0;

                }

                //下滑超过边界,显示最后一个

                if (choosedPosition >= firstLetterssize()) {

                    choosedPosition = firstLetterssize() - 1;

                }

                if (listener != null) {

                    //滑动A-Z字母联动外层数据

                    listeneronTouch(firstLettersget(choosedPosition));

                }

                break;

            case MotionEventACTION_UP:

                thissetBackgroundColor(contextgetResources()getColor(androidRcolortransparent));

                choosedPosition = -1;

                if (listener != null) {

                    //滑动A-Z字母联动外层数据

                    listeneronRelease();

                }

                break;

        }

        //重绘

        invalidate();

        return true;

    }

    public void setFirstListener(OnTouchFirstListener listener) {

        thislistener = listener;

    }

    /

    OnTouchFirstListener 接口

    onTouch:触摸到了那个字母

    onRelease:up释放时中间显示的字母需要设置为GONE

    /

    public interface OnTouchFirstListener {

        void onTouch(String firstLetter);

        void onRelease();

    }

}

<declare-styleable name="SlideBar">

    <attr name="letter_size" format="dimension" />

    <attr name="letter_height" format="dimension" />

    <attr name="letter_gap" format="dimension" />

    <attr name="letter_bg_radius" format="dimension" />

</declare-styleable>

xml中引入,我的是constraintlayout,具体设置看自己的布局

<comanswerviewSlideBar

    android:id="@+id/slideBar"

    android:layout_width="@dimen/dp_20"

    android:layout_height="wrap_content"

    app:layout_constraintBottom_toBottomOf="@+id/tiku_recycle_answer"

    app:layout_constraintEnd_toEndOf="parent"

    app:layout_constraintStart_toStartOf="@+id/guide_answer"

    app:layout_constraintTop_toTopOf="@+id/tiku_recycle_answer"

    app:letter_bg_radius="@dimen/dp_8"

    app:letter_gap="@dimen/dp_6"

    app:letter_height="@dimen/dp_10"

    app:letter_size="@dimen/sp_10" />

private void handleSlideBarEvent() {

    List<QuesCommentSubjectiveStuBean> datas = subjectiveCommentDetailAdaptergetDatas();//获取处理后的数据,赋值给导航栏

    ArrayList<String> letters = new ArrayList<>();

    for (QuesCommentSubjectiveStuBean stuBean : datas) {

        if (letterscontains(stuBeangetFirstLetter())) {

            continue;

        }

        lettersadd(stuBeangetFirstLetter());

    }

    slideBarsetFirstLetters(letters);

    slideBarsetTiku_recycle_answer(tiku_recycle_answer);

    slideBarsetFirstListener(new SlideBarOnTouchFirstListener() {

        @Override

        public void onTouch(String firstLetter, float dy) {

            tv_first_lettersetVisibility(VISIBLE);

            tv_first_lettersetText(firstLetter);

            ConstraintLayoutLayoutParams layoutParams = (ConstraintLayoutLayoutParams) tv_first_lettergetLayoutParams();

            //如果是第一个字母,修改提示框显示位置

            layoutParamstopMargin = (int) dy + slideBargetTop() - tv_first_lettergetMeasuredHeight() / 2;

            //异常情况,点击最后一个字符,提示框显示不全的场景,如果显示位置超过屏幕,则靠底部显示

            if ((int) dy + slideBargetTop() + tv_first_lettergetMeasuredHeight() / 2 > tiku_recycle_answergetBottom()) {

                layoutParamstopMargin = tiku_recycle_answergetBottom() - tv_first_lettergetMeasuredHeight();

            }

            tv_first_lettersetLayoutParams(layoutParams);

            //滑动后移动到对应的位置,找到第一个匹配到首字母的学生,位移到此处

            int newPosition = -1;

            for (QuesCommentSubjectiveStuBean stuBean : datas) {

                if (firstLetterequals(stuBeangetFirstLetter())) {

                    newPosition = datasindexOf(stuBean);

                    break;

                }

            }

            //move时会多次触发,此处只响应第一次

            if (newPosition != lastPosition) {

                lastPosition = newPosition;

                Lgd(TAG, "questionComment-->--滑动导航栏跳转到首字母:" + firstLetter);

                subJectLinearLayoutManagerscrollToPositionWithOffset(lastPosition, 0);

            }

        }

        @Override

        public void onRelease() {

            postDelayed(new Runnable() {

                @Override

                public void run() {

                    lastPosition = -1;

                    tv_first_lettersetVisibility(GONE);

                }

            }, 200);

        }

    });

}

5一个小问题。

用于放大显示选中字母的TextView在布局中,请设置为invisible,这样在加载xml布局时,会对这个控件进行测量和布局,只是不显示,这样我们才能获得tv_first_lettergetMeasuredHeight(),如果设置为gone,不会进行测量,获取的高度就为0,这样在第一次显示的时候就会有一个显示位置跳动的异常。设置为invisible就可以解决这个问题,目的就是让系统测量一下TextView的宽高,不想这么搞的话,在第4步之前手动测量一次也是可以的。

<TextView

    android:id="@+id/tv_first_letter"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_marginEnd="@dimen/dp_2"

    android:background="@mipmap/ic_bubble"

    android:fontFamily="sans-serif"

    android:gravity="center"

    android:text="C"

    android:textColor="@color/color_ffffff"

    android:textSize="@dimen/sp_18"

    android:visibility="invisible"

    app:layout_constraintEnd_toStartOf="@+id/guide_answer"

    app:layout_constraintTop_toTopOf="parent" />

public class MainActivity extends ActionBarActivity {

    private static final String PATH = EnvironmentgetExternalStorageDirectory() + "/123json";

    private androidwidgetTextView jsonTv;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        superonCreate(savedInstanceState);

        setContentView(Rlayoutactivity_main);

        thisjsonTv = (TextView) findViewById(RidjsonTv);

        new Thread() {

            @Override

            public void run() {

                Message msg = handlerobtainMessage();

                msgobj = getFileFromSD(PATH);

                msgwhat = 1;

                handlersendMessage(msg);

            }

        }start();

    }

    private Handler handler = new Handler() {

        @Override

        public void handleMessage(Message msg) {

            if (msgwhat == 1) {

                String result = (String) msgobj;

                try {

                    JSONObject jObj = new JSONObject(result);

                    jsonTvsetText("name:"+jObjgetString("name")+",age:"+jObjgetInt("age"));

                } catch (JSONException e) {

                    eprintStackTrace();

                }

            }

        }

    };

    private String getFileFromSD(String path) {

        String result = "";

        try {

            FileInputStream f = new FileInputStream(path);

            BufferedReader bis = new BufferedReader(new InputStreamReader(f));

            String line = "";

            while ((line = bisreadLine()) != null) {

                result += line;

            }

        } catch (Exception e) {

            eprintStackTrace();

        }

        return result;

    }

}<RelativeLayout xmlns:android="

    xmlns:tools="

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:paddingBottom="@dimen/activity_vertical_margin"

    android:paddingLeft="@dimen/activity_horizontal_margin"

    android:paddingRight="@dimen/activity_horizontal_margin"

    android:paddingTop="@dimen/activity_vertical_margin"

    tools:context="MainActivity">

    <TextView

        android:id="@+id/jsonTv"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        />

</RelativeLayout>

最后,记得在AndroidMenifest文件中加入权限

<uses-permission android:name="androidpermissionREAD_EXTERNAL_STORAGE"/>

123json的内容

{"name":"rock","age":20}

通过setText方法显示外部数据。

1、获取要显示数据的TextView组件

TextView tv = (TextView)findViewById(Ridxxxx);//根据id获取TextView组件

2、通过setText方法显示数据

tvsetText("要显示的数据内容");//设置字符串显示

android无法获取res资源文件夹路径,只能通过系统提供的封装函数访问。

资源文件夹有:

/res/drawable

,通过getresources()访问

/res/values

,通过getresources()访问

/res/layout,通过getresources()访问

/res/xml,通过getresources()访问

/res/raw,通过getresources()访问

/assets,通过getassets()访问

以上就是关于Android O Launcher3-Workspace加载全部的内容,包括:Android O Launcher3-Workspace加载、Android自定义字母导航栏、跪求 一个Android 读取SD卡里的 json 文件的解析,要完整代码等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9347337.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-27
下一篇 2023-04-27

发表评论

登录后才能评论

评论列表(0条)

保存