一简述:
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 文件的解析,要完整代码等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)