嗯,这个标题栏,可以说是我在这个项目中学习到的最大的东西。他告诉我一个道理:技术这个东西,越小越精致。
要达到的效果:每个项目都有一个标题头,将这个表头做成一个样式来疯狂复用。
遗憾的是,我只做到了初步的了解,但是如何适应整个屏幕我还是没有做到,看来生命不休学习不能止!
严谨的代码就要有严谨的过程,事情果然是要一步一步做的。
1 首先,要创建一个咩有标题栏的项目:
重写applicaiton,
import android.app.Activity; import android.content.res.Resources; import com.yanzhenjie.nohttp.Logger; import com.yanzhenjie.nohttp.NoHttp; import java.util.ArrayList; /** * Created by SensYang on 2017/05/23 10:16 */ public class MZApplication extends MultiDexApplication { private static MZApplication instance; private ArrayList父类:activityList = new ArrayList<>(); @Override public void onCreate() { super.onCreate(); NoHttp.initialize(this); Logger.setDebug(true); // 开启NoHttp调试模式。 Logger.setTag("Omi_NoHttp--->"); // 设置NoHttp打印Log的TAG。 } public static MZApplication getInstance(){ return instance; } private Resources resources; @Override public Resources getResources() { if (resources == null) resources = super.getResources(); return resources; } /** * 记录一条Activity信息 */ public void addActivity(Activity activity) { if (!activityList.equals(activity)) { activityList.add(activity); } } /** * 删除一条Activity的信息 */ public void removeActivity(Activity activity) { activityList.remove(activity); } @Override public void onTerminate() { clearActivitys(); super.onTerminate(); System.exit(0); } /** * 清空所有activity */ public void clearActivitys() { for (Activity activity : activityList) { activity.finish(); } activityList.clear(); } }
import android.app.Application; import android.content.Context; import android.support.multidex.MultiDex; public class MultiDexApplication extends Application { public MultiDexApplication() { } protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this);//需要在清单文件中添加引用声明:compile 'com.android.support:multidex:+' } }
在清单文件中,声明使用这个重写的applicaiton。
android:name=".MZApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/LauncherTheme"
>
所有,这个theme的style也需要自己去写了,这里刚好有每个界面的开启关闭动画。
在资源文件中的style下
看到这里就知道,每个界面的开启动画关闭动画都在这里直接设置:
xml version="1.0" encoding="utf-8"?>xmlns:android="http://schemas.android.com/apk/res/android" android:duration="300" android:fromXDelta="100%p" android:toXDelta="0" />
xml version="1.0" encoding="utf-8"?>xmlns:android="http://schemas.android.com/apk/res/android" android:duration="300" android:fromXDelta="0" android:toXDelta="-25%p" />
xml version="1.0" encoding="utf-8"?>xmlns:android="http://schemas.android.com/apk/res/android" android:duration="300" android:fromXDelta="-25%p" android:toXDelta="0" />
xml version="1.0" encoding="utf-8"?>xmlns:android="http://schemas.android.com/apk/res/android" android:duration="300" android:fromXDelta="0.0" android:toXDelta="100.0%p" />
至此,第一步结束:得到了一个没有标题栏的项目,下一步:创建一个标题栏!
2 创建标题栏
2.1 标题栏的databinding布局:
xml version="1.0" encoding="utf-8"?>xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> name="isShowNewDot" type="Boolean"/> android:layout_width="match_parent" android:layout_height="50dp"> android:id="@+id/leftRL" android:layout_width="wrap_content" android:layout_height="match_parent" android:minWidth="33dp" android:paddingLeft="15dp"> android:id="@+id/leftIV" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_marginBottom="15dp" android:layout_marginTop="15dp" android:adjustViewBounds="true" android:paddingRight="3.5dp" tools:ignore="ContentDescription"/> android:id="@+id/leftNewDot" android:layout_width="7dp" android:layout_height="7dp" android:layout_alignRight="@id/leftIV" android:layout_alignTop="@id/leftIV" android:layout_marginTop="-3.5dp" android:visibility="gone"/> android:id="@+id/leftTV" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_toRightOf="@id/leftIV" android:gravity="right|center_vertical" android:lines="1" android:textColor="@color/white" android:textSize="16sp"/> android:id="@+id/centerTV" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_centerInParent="true" android:ellipsize="marquee" android:focusable="true" android:focusableInTouchMode="true" android:gravity="center" android:marqueeRepeatLimit="marquee_forever" android:maxLines="1" android:scrollHorizontally="true" android:textColor="@color/white" android:textSize="19sp"/> android:id="@+id/topBarRightSecond" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_marginBottom="15dp" android:layout_marginTop="15dp" android:layout_toLeftOf="@+id/rightLL" android:visibility="gone" tools:ignore="ContentDescription"/> android:id="@+id/rightLL" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_alignParentRight="true" android:minWidth="33dp" android:orientation="horizontal" android:paddingRight="15dp"> android:id="@+id/rightTV" android:layout_width="wrap_content" android:layout_height="match_parent" android:background="@null" android:gravity="right|center_vertical" android:lines="1" android:textColor="@color/white" android:textSize="16sp"/> android:id="@+id/rightIV" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_marginBottom="15dp" android:layout_marginTop="15dp" android:adjustViewBounds="true" tools:ignore="ContentDescription"/>
2.2 标题栏的代码:
有两个特别的地方
import android.content.Context; import android.content.res.TypedArray; import android.databinding.DataBindingUtil; import android.os.Build; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import itcast.zz.kdniaodemo.databinding.WidgetTopNavigationBarBinding; /** * Created by SensYang on 2016/6/3. */ public class TopNavigationBar extends FrameLayout { private boolean hasSetStatusBarHeight = false; private float alpha = 1; private int color; private WidgetTopNavigationBarBinding navigationBarBinding; public TopNavigationBar(Context context) { this(context, null); } public TopNavigationBar(Context context, AttributeSet attrs) { super(context, attrs); color = Config.getMain_color(); if (color != -1) setBackgroundColor(color); else setBackgroundResource(R.color.main_color); navigationBarBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.widget_top_navigation_bar, this, true); // 初始化属性 this.initializeAttributes(context, attrs); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (!hasSetStatusBarHeight) { hasSetStatusBarHeight = true; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { int left = getPaddingLeft(); int top = getPaddingTop() + DisplayUtil.getStatusBarHeight();//这里,是确定标题栏距离顶部距离的地方 int right = getPaddingRight(); int bottom = getPaddingBottom(); setPadding(left, top, right, bottom); getLayoutParams().height = LayoutParams.WRAP_CONTENT; } setBackgroundAlpha(alpha); } } public void setBackgroundAlpha(float alpha) { this.alpha = alpha; if (getBackground() != null) { getBackground().setAlpha((int) (alpha * 255)); } } //DeclareOnClickListener 是重写的一个onClick事件,下面有介绍! public void setLeftClick(String methodName) { if (methodName != null) { //所以这里也是一个重点 navigationBarBinding.leftRL.setOnClickListener(new DeclaredOnClickListener(navigationBarBinding.leftRL, methodName)); } } public void setRightClick(String methodName) { if (methodName != null) { navigationBarBinding.rightLL.setOnClickListener(new DeclaredOnClickListener(navigationBarBinding.rightLL, methodName)); } } public void setLeftClick(OnClickListener listener) { navigationBarBinding.leftRL.setOnClickListener(listener); } public void setRightClick(OnClickListener listener) { navigationBarBinding.rightLL.setOnClickListener(listener); } public int calculateHeight() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) return getResources().getDimensionPixelOffset(R.dimen.topbar_height) + DisplayUtil.getStatusBarHeight(); else return getResources().getDimensionPixelOffset(R.dimen.topbar_height); } private void initializeAttributes(Context context, AttributeSet attrs) { if (attrs == null) return; TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.OmiTheme); boolean visibility; int resourceId; resourceId = typedArray.getResourceId(R.styleable.OmiTheme_backGround, -1); if (resourceId != -1) { this.setBackgroundResource(resourceId); } navigationBarBinding.leftTV.setText(typedArray.getText(R.styleable.OmiTheme_leftText)); visibility = typedArray.getBoolean(R.styleable.OmiTheme_leftVisible, true); if (!visibility) { navigationBarBinding.leftIV.setVisibility(View.GONE); } resourceId = typedArray.getResourceId(R.styleable.OmiTheme_leftSrc, -1); if (resourceId != -1) { navigationBarBinding.leftIV.setImageResource(resourceId); } navigationBarBinding.centerTV.setText(typedArray.getText(R.styleable.OmiTheme_centerText)); visibility = typedArray.getBoolean(R.styleable.OmiTheme_rightVisible, true); resourceId = typedArray.getResourceId(R.styleable.OmiTheme_rightSrc, -1); navigationBarBinding.rightTV.setText(typedArray.getText(R.styleable.OmiTheme_rightText)); if (!visibility) { navigationBarBinding.rightIV.setVisibility(View.GONE); navigationBarBinding.topBarRightSecond.setVisibility(View.GONE); } if (resourceId != -1) { navigationBarBinding.rightIV.setImageResource(resourceId); } resourceId = typedArray.getResourceId(R.styleable.OmiTheme_rightSecondSrc, -1); if (resourceId != -1) { navigationBarBinding.topBarRightSecond.setVisibility(View.VISIBLE); navigationBarBinding.topBarRightSecond.setImageResource(resourceId); } typedArray.recycle(); } public TextView getLeftTV() { return navigationBarBinding.leftTV; } public ImageView getLeftIV() { return navigationBarBinding.leftIV; } public TextView getCenterTV() { return navigationBarBinding.centerTV; } public TextView getRightTV() { return navigationBarBinding.rightTV; } public ImageView getRightIV() { return navigationBarBinding.rightIV; } /** * 设置左边新消息显示状态 */ public void setLeftDotVisibility(boolean isVisibility) { navigationBarBinding.leftNewDot.setVisibility(isVisibility ? VISIBLE : GONE); } public void setLeftText(int textRes) { if (navigationBarBinding.leftTV != null) navigationBarBinding.leftTV.setText(textRes); } public void setLeftText(CharSequence text) { if (navigationBarBinding.leftTV != null) navigationBarBinding.leftTV.setText(text); } public void setCenterText(int textRes) { if (navigationBarBinding.centerTV != null) navigationBarBinding.centerTV.setText(textRes); } public void setCenterText(CharSequence text) { if (navigationBarBinding.centerTV != null) navigationBarBinding.centerTV.setText(text); } public void setRightText(int textRes) { if (navigationBarBinding.rightTV != null) navigationBarBinding.rightTV.setText(textRes); } public void setRightText(CharSequence text) { if (navigationBarBinding.rightTV != null) navigationBarBinding.rightTV.setText(text); } public void setRightImageResource(int resId) { if (navigationBarBinding.rightIV != null) { navigationBarBinding.rightIV.setImageResource(resId); } } public void setTopBound(){ this.setBackgroundResource(R.color.BEBEBE); } }
2.2.1 如何获取标题栏到顶部的距离,上面用到的
DisplayUtil
import android.content.Context; import android.content.res.Resources; import android.util.DisplayMetrics; import android.view.WindowManager; import java.lang.reflect.Field; public class DisplayUtil { private static DisplayMetrics displaysMetrics = null; private static float scale = -1.0f; public static DisplayMetrics getDisplayMetrics() { if (null == displaysMetrics) { displaysMetrics = new DisplayMetrics(); WindowManager wm = (WindowManager) MZApplication.getInstance().getSystemService(Context.WINDOW_SERVICE); wm.getDefaultDisplay().getMetrics(displaysMetrics); } return displaysMetrics; } /** * 获取屏幕高度 */ public static int getHeight() { if (null == displaysMetrics) { getDisplayMetrics(); } return displaysMetrics.heightPixels; } private static int getStatusBarHeight(Resources resources) { Class> c; Object obj; Field field; int x, sbar = 0; try { c = Class.forName("com.android.internal.R$dimen"); obj = c.newInstance(); field = c.getField("status_bar_height"); x = Integer.parseInt(field.get(obj).toString()); sbar = resources.getDimensionPixelSize(x); } catch (Exception e1) { e1.printStackTrace(); } return sbar; } private static int statusBarHeight = 0; /** * 获取状态栏高度 */ public static int getStatusBarHeight() { if (statusBarHeight == 0 && MZApplication.getInstance() != null) { statusBarHeight = getStatusBarHeight(MZApplication.getInstance().getResources()); } return statusBarHeight; } /** * 获取屏幕宽度 */ public static int getWidth() { if (null == displaysMetrics) { getDisplayMetrics(); } return displaysMetrics.widthPixels; } /** * 根据手机的分辨率从 dp 的单位 转成为 px(像素) */ public static float dip2px(float dpValue) { if (scale < 0) { if (null == displaysMetrics) { getDisplayMetrics(); } scale = displaysMetrics.density; } return dpValue * scale + 0.5f; } /** * 根据手机的分辨率从 px(像素) 的单位 转成为 dp */ public static float px2dip(float pxValue) { if (scale < 0) { if (null == displaysMetrics) { getDisplayMetrics(); } scale = displaysMetrics.density; } return pxValue / scale + 0.5f; } /** * 根据手机的分辨率从sp 的单位 转成为 dp */ public static float sp2px(float spValue) { if (null == displaysMetrics) { getDisplayMetrics(); } float fontScale = displaysMetrics.scaledDensity; return spValue * fontScale + 0.5f; } }
2.2.2 上边用到的重写的点击事件:
import android.content.Context; import android.content.ContextWrapper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.View; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * Created by SensYang on 2017/05/23 11:18 */ public class DeclaredOnClickListener implements View.OnClickListener { private final View mHostView; private final String mMethodName; private Method mResolvedMethod; private Context mResolvedContext; public DeclaredOnClickListener(@NonNull View hostView, @NonNull String methodName) { mHostView = hostView; mMethodName = methodName; } @Override public void onClick(@NonNull View v) { if (mResolvedMethod == null) { resolveMethod(mHostView.getContext(), mMethodName); } try { mResolvedMethod.invoke(mResolvedContext, v); } catch (IllegalAccessException e) { throw new IllegalStateException( "Could not execute non-public method for android:onClick", e); } catch (InvocationTargetException e) { throw new IllegalStateException( "Could not execute method for android:onClick", e); } } @NonNull private void resolveMethod(@Nullable Context context, @NonNull String name) { while (context != null) { try { if (!context.isRestricted()) { final Method method = context.getClass().getMethod(mMethodName, View.class); if (method != null) { mResolvedMethod = method; mResolvedContext = context; return; } } } catch (NoSuchMethodException e) { // Failed to find method, keep searching up the hierarchy. } if (context instanceof ContextWrapper) { context = ((ContextWrapper) context).getBaseContext(); } else { // Can't search up the hierarchy, null out and fail. context = null; } } final int id = mHostView.getId(); final String idText = id == View.NO_ID ? "" : " with id '" + mHostView.getContext().getResources().getResourceEntryName(id) + "'"; throw new IllegalStateException("Could not find method " + mMethodName + "(View) in a parent or ancestor Context for android:onClick " + "attribute defined on view " + mHostView.getClass() + idText); } }这个类中,可以根据传递进去的方法的名字进行判断,如果类中有这个方法就直接使用。
在使用的时候需要用到一些命名规范,这是使用之前的最后一步:
values 下创建文件夹:attrs:
xml version="1.0" encoding="utf-8"?>name="OmiTheme"> name="slideAble" format="boolean"/> name="backGround" format="reference|color"/> name="leftText" format="reference|string"/> name="leftVisible" format="boolean"/> name="leftSrc" format="reference"/> name="centerText" format="reference|string"/> name="rightText" format="reference|string"/> name="rightVisible" format="boolean"/> name="rightSrc" format="reference"/> name="rightSecondSrc" format="reference"/> name="textSize" format="dimension"/> name="textColor" format="color"/> name="selectTextColor" format="color"/> name="CircleImageView"> name="border_width" format="dimension"/> name="border_color" format="color"/>
使用这个标题栏:
android:id="@+id/topNavigationBar"
style="@style/TopBarStyle"
omi:centerText="@string/dynamic"
omi:leftClick="@{@string/top_left_click}"
omi:leftSrc="@mipmap/back_btn"
omi:rightClick="@{@string/top_right_click}"
omi:rightSrc="@mipmap/dynamic_edit"/>
实际使用:
xml version="1.0" encoding="utf-8"?>xmlns:android="http://schemas.android.com/apk/res/android" xmlns:omi="http://schemas.android.com/apk/res-auto" > android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > android:id="@+id/topNavigationBar" style="@style/TopBarStyle" omi:centerText="主页面" omi:leftSrc="@mipmap/back_btn" omi:leftClick="@{@string/top_left_click}" />
是的,就是这个小小的标题栏,给他设置一个宽高,就可以了:
在使用的那个界面中重写两个方法如果有必要的话:
omi:leftClick="@{@string/top_left_click}"这句的左右就是,top_left_click这句string是什么,标题栏就会去引用他的这个界面去找有没有方法名字叫top_left_click的,如果有,就直接给左侧的图片自动加上点击事件,比如 finish();
public void topLeftClick(View view) { finish(); }