无论是定制系统还是自行开发APP的UI,其无论是使用标准UI还是自定义UI,最终都是需要自己熟悉主题风格的各种属性设置,不过属性非常的多,如果需要知道某个UI可以临时查看一下SDK的
D:\liuzhibao\Android\sdk\platforms\android-N\data\res路径下的,但是这个是纯粹的资源文件,没有java文件,所以还是推荐repo下来framework/base代码.
下面先看看自定义View如何添加属性之类的:
新建一个PumpKinCustomeView的android studio工程:
主类程序:
package org.durian.pumpkincustomeview; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class PumpKinMainActivity extends AppCompatActivity { private Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_pump_kin_main); mButton=(Button)findViewById(R.id.button); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent=new Intent(PumpKinMainActivity.this,ThemeActivity.class); PumpKinMainActivity.this.startActivity(intent); finish(); } }); } }
既然要自定义View,那么就需要新建一个atts.xml文件到res/values下,下面随便举几个:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="UIView"> <attr name="pumpkin_background" format="color"/> <attr name="pumpkin_textcolor" format="color" /> <attr name="pumpkin_textsize" format="integer"/> <attr name="pumpkin_view_width" format="dimension"/> <attr name="pumpkin_view_height" format="dimension"/> <attr name="pumpkin_text" format="string"/> <attr name="pumpkin_draw" format="reference"/> <attr name="pumpkin_enable" format="boolean"/> <attr name="pumpkin_style" format="reference" /> <attr name="pumpkin_enum" format="enum" /> <attr name="pumpkin_float" format="float" /> <attr name="pumpkin_fraction" format="fraction" /> </declare-styleable> </resources>
上面基本上一看还是非常清楚的,上面只缺少flag位运算format,fraction为百分比,reference为引用,enum可以如下:
<declare-styleable name="pumpkin_enum_style"> <attr name="pumpkin_enum"> <enum name="enum1">1</enum> <enum name="enum2">2</enum> <enum name="enum3">3</enum> </attr> </declare-styleable>
定义好上面的属性后,新建一个UIView的继承View的类:
package org.durian.pumpkincustomeview.view; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import org.durian.pumpkincustomeview.R; /** * Project name : PumpKinCustomeView * Created by zhibao.liu on 2016/4/28. * Time : 10:32 * Email [email protected] * Action : durian */ public class UIView extends View { private Paint mPaint; private Paint mTextPaint; private int rectWidth; private int rectHeight; public UIView(Context context) { super(context); } public UIView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.UIView); int textSize=typedArray.getInteger(R.styleable.UIView_pumpkin_textsize,128); int textColor=typedArray.getColor(R.styleable.UIView_pumpkin_textcolor, Color.GRAY); mTextPaint.setColor(textColor); mTextPaint.setStrokeWidth(2); mTextPaint.setTextSize(textSize); int backgroundColor=typedArray.getColor(R.styleable.UIView_pumpkin_background,Color.GRAY); rectWidth=(int)typedArray.getDimension(R.styleable.UIView_pumpkin_view_width,512); rectHeight=(int)typedArray.getDimension(R.styleable.UIView_pumpkin_view_height,513); rectWidth=Utils.px2dip(context,rectWidth); rectHeight=Utils.px2dip(context,rectHeight); //rectHeight=Utils.dip2px(context,rectHeight); mPaint.setColor(backgroundColor); android.util.Log.i("UIVIEW","rectWidth : "+rectWidth+" rectHeight : "+rectHeight); } public void initView(Context context){ mPaint=new Paint(); mTextPaint=new Paint(); } @Override public void draw(Canvas canvas) { super.draw(canvas); canvas.drawRect(new Rect(10,10,rectWidth,rectHeight),mPaint); canvas.drawText("hello view",50,800,mTextPaint); } }
然后在工程主Activity的布局中添加这个自定义View.在添加之前,先为这个View写一个自定义style:
<!-- add by pumpkin custom style --> <style name="Pumpkin_style"> <item name="pumpkin_background">#ffff0000</item> <item name="pumpkin_textcolor">#ff00ff00</item> <item name="pumpkin_view_width">500dp</item> <item name="pumpkin_view_height">500dp</item> </style>
然后添加到主Activity的布局中:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:pumpkin="http://schemas.android.com/apk/res-auto" 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" android:orientation="vertical" tools:context="org.durian.pumpkincustomeview.PumpKinMainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="theme"/> <org.durian.pumpkincustomeview.view.UIView android:layout_width="wrap_content" android:layout_height="wrap_content" pumpkin:pumpkin_textsize="128" style="@style/Pumpkin_style" /> </LinearLayout>
注意:
<org.durian.pumpkincustomeview.view.UIView android:layout_width="wrap_content" android:layout_height="wrap_content" pumpkin:pumpkin_textsize="128" style="@style/Pumpkin_style" />
这里故意分开一部分直接写在布局中,有一部分属性赋值封装到PumKin_style,但是直接写到布局中需要注意比如(pumpkin:pumpkin_textsize):
在布局头部需要标记路径:现在android studio可以直接res-auto,以前需要换成res/报名
xmlns:pumpkin=<a target=_blank href="http://schemas.android.com/apk/res-auto">http://schemas.android.com/apk/res-auto</a>
在自定义UIView中注意:
rectWidth=Utils.px2dip(context,rectWidth); rectHeight=Utils.px2dip(context,rectHeight);
注意: 我们在风格中明明写着是500,但是如果没有上面两句程序,RectWidth的值将是1500,也就是这个地方设置的500dp(预想这样),但是打印log出来将是1500px,结果整个屏幕的方形将非常的大,也不是我们所需要的,所以获取尺寸以后需要转一下从px转到pd单位,所以这个地方需要特别注意.字体也存在这个转换,但是上面程序没有在给出了,转换一般如下:
package org.durian.pumpkincustomeview.view; import android.content.Context; /** * Project name : PumpKinCustomeView * Created by zhibao.liu on 2016/4/28. * Time : 14:42 * Email [email protected] * Action : durian */ public class Utils { public static int dip2px(Context context, float dipValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dipValue * scale + 0.5f); } public static int px2dip(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); } }
运行结果:
下面来看看如果是系统UI的属性如何修改:下面android5.0来简说,
加入我的Activity有CheckBox和EditText,我希望这个Activity中所有的CheckBox都是一致的,所有的EditText风格也是一致的,如何处理呢?
系统资源设计有几个基本原则和规律:
<1> : 具有继承性:如
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style>
使用parent关键字,继承parent的所有属性值.
<2> : 子类属性覆盖父类属性值:即在虽然在父类对某个属性赋值了,但是子类希望在这个属性值上面不同于父类,那么就在子类重新对改属性值赋值,如下:
<style name="CustomeAppTheme" parent="@style/AppTheme"> <item name="colorPrimary">#ffff0000</item> <!--<item name="colorAccent">#fff4e80b</item>--> <item name="editTextStyle">@style/pumpkin_editTextStyle</item> <item name="checkboxStyle">@style/pumpkin_checkboxStyle</item> </style>
属性colorPrimary在父类AppTheme中已经赋了值,但还是我希望自己的Activity的titlebar的颜色是红色,那么在这里就重新赋值,达到覆盖父类那个属性值的目的.
<3> : 回答最上面那个问题,如果需要调整一个页面内所以某个UI的风格,那么就需要重新将UI的风格重新写,但是一般推荐继承父类,只调整需要调整的部分,其他的仍然采用父类的属性,这样就不要从零开始写了:
<style name="pumpkin_editTextStyle" parent="@android:style/Widget.Material.EditText"> <item name="android:textColorPrimary">#ffff0000</item> <item name="android:textColor">#ff11e7f3</item> <item name="android:textSize">48sp</item> </style> <style name="pumpkin_checkboxStyle" parent="@android:style/Widget.Material.CompoundButton.CheckBox"> <item name="android:textColor">#fff107f3</item> <item name="android:textSize">32sp</item> </style>
将页面的EditText和CheckBox的几个属性重新调整.
在新建一个Activity测试一下:
package org.durian.pumpkincustomeview; import android.content.Intent; import android.content.res.Resources; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class ThemeActivity extends AppCompatActivity { private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_theme); button=(Button)findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent=new Intent(ThemeActivity.this,CompareThemeActivity.class); ThemeActivity.this.startActivity(intent); } }); } }
manifest.xml添加自定义主题给它:
<activity android:name=".ThemeActivity" android:theme="@style/CustomeAppTheme"></activity>
对应的布局:
<?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" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:orientation="vertical" tools:context="org.durian.pumpkincustomeview.ThemeActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="hello textview"/> <CheckBox android:text="hello checkbox" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/button" android:text="button" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
运行结果:
发现颜色有54%透明度的黑色变为指定的颜色了.
有读者可能又会问题,如何知道EditText等的属性风格,比如
<item name="editTextStyle">
这个地方记住顺序:
打开android源代码,查看EditText.java源代码:
public EditText(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.editTextStyle); }
看到了吧,这就是为什么前面要自己先写一个自定义的UIView,回顾一下这些主题风格是如何被UI使用的,看到上面的editTextStyle,然后搜索:
<item name="editTextStyle">@style/Widget.Material.EditText</item>
我的是android5.0的代码,所以带有material.
同时搜索的时候一定要注意,这个搜索的关键字一定要出现在Public.xml中,因为android系统后面的资源需要声明,如果声明在Public.xml中才能够被APP developer使用,即公共的,如果是在symbol.xml,那就算是系统内部自己使用,即私有的.很显然我们这里是需要在APP中使用,所以一定是需要公有的才行.在自己APP程序重新再写这些属性值即可覆盖framework中预设的.
假如自己的UI的一些属性在系统中声明为私有的,怎么办,最粗暴的做法就是直接将系统中的风格和资源文件全部挑选出来放到自己的APP资源下,单个UI的话并不是很难.
假设,这里只是个假设SeekBar 这个ui风格是私有的,那么步骤如下:
<1> : 查看SeekBar.java这个类:
public SeekBar(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.seekBarStyle); }
继续看seekBarStyle风格:
<item name="seekBarStyle">@style/Widget.Material.SeekBar</item>
找到"定义处":
<style name="Widget.Material.SeekBar"> <item name="indeterminateOnly">false</item> <item name="progressDrawable">@drawable/seekbar_track_material</item> <item name="indeterminateDrawable">@drawable/seekbar_track_material</item> <item name="thumb">@drawable/seekbar_thumb_material_anim</item> <item name="splitTrack">true</item> <item name="useDisabledAlpha">false</item> <item name="focusable">true</item> <item name="paddingStart">16dip</item> <item name="paddingEnd">16dip</item> <item name="mirrorForRtl">true</item> <item name="background">@drawable/control_background_32dp_material</item> </style>
其实一般改的并不多,一般是修改SeekBar的进度线的风格(粗细,颜色等)以及游标图案或者颜色,将上面的copy到自己的工程
假如修改背景颜色,那么就还需要提取这个资源:
@drawable/control_background_32dp_material
这个想都不用想,绝对是个xml文件:
<?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2015 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/control_highlight_material" android:radius="16dp" />
一看,control_highlight_material还是一个xml:
<?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2014 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_checked="true" android:state_enabled="true" android:alpha="@dimen/highlight_alpha_material_colored" android:color="?attr/colorControlActivated" /> <item android:color="?attr/colorControlHighlight" /> </selector>
也需要提取出来.继续查找颜色:
<item name="colorControlActivated">?attr/colorAccent</item>
一看colorAccent这个颜色值,这个颜色值很疯狂的,是一个全局的,比如你在自己的APP主题中设置:
<item name="colorAccent">#fff4e80b</item>
你会发现你的所有UI都变色了,而不是一个.
继续查看另外一个颜色colorCotrolHighlight:
<item name="colorControlHighlight">@color/ripple_material_dark</item>
这个颜色值也是一个xml:
<?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2015 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:alpha="@dimen/highlight_alpha_material_dark" android:color="@color/foreground_material_dark" /> </selector>
再来看看foreground_material_dark,最终是
<color name="foreground_material_dark">@color/white</color>
即白色前景.
了解上面的流程,如果参考系统的UI就可以拷贝出来自行调整各自的值.
当然上面都是调整APP部分的属性,如果是定制系统,那么就可以在vendor/overlay/framework中自行开发,这样整个系统都有了,但是一般这样的都要拷贝系统原生的,如果希望自己定制的系统中运行的第三方app也跟着调整,也可以直接改原生的主题风格,将原生的代码改掉,当然一般不推荐.
后面一篇可以看看系统基本UI都是谁演化而来:
Button extends TextView
TextView extends View