上一篇:Android 天气APP(十二)空气质量、UI优化调整
自定义背景,做这个功能的原因是因为一些人觉得必应的每日一图并不好看,想要手动上传自己手机里的壁纸作为背景,并且应用也要有自带的壁纸供用户选择。正所谓有需就有求,这是亘古不变的道理,第三个就是UI的优化,这次我是打算把切换城市的弹窗挪到二级菜单里面,右上角做一个一级菜单列表,这个列表暂定功能为切换城市和切换背景,这样做也是符合大众APP的审美,比如微信、支付宝、QQ之类的。
不知道你们是不是被这个仿微信弹窗的内容吸引进来的,是的话,你就来对地方了。我不是标题党,我是踏踏实实的程序员,不搞那些花里胡哨的文章标题,实事求是,我觉那种标题吸引别人进来看,结果什么有用的知识都没有,得浪费别人的时间等同于诈骗。
首先在项目的layout布局文件中创建一个弹窗的布局
window_add.xml,弹窗的背景图片是一个.9可拉伸的png图片,
一黑一白,想用自取即可,或者可以去GitHub上面源码里去取图。
在上一篇文章中,在mvplibrary中的res文件下新建了colors.xml,并在里面新增几个颜色进去,所以为了更好的管理项目中的颜色,后续的颜色都会写在这里,其他页面通过@color/black来调用即可,
然后再创建一个dimen.xml文件,这个里面主要防止尺寸大小和字体大小
调用方式通过@dimen/dp_10或者@dimen/sp_10
因为下面的布局文件中会涉及到这两个xml里面的内容,所以我这里会说的比较清楚。
colors.xml
"1.0" encoding="utf-8"?>
"arc_bg_color">#C6D7F4
"arc_progress_color">#FBFEF7
"white">#ffffff
"black">#000000
"blue_one">#9FC8E9
"transparent">#00000000
"transparent_bg">#22000000
dimen.xml
"1.0" encoding="utf-8"?>
"dp_0">0dp
"dp_0_1">0.1dp
"dp_0_5">0.5dp
"dp_1">1dp
"dp_1_5">1.5dp
"dp_2">2dp
"dp_2_5">2.5dp
"dp_3">3dp
"dp_3_5">3.5dp
"dp_4">4dp
"dp_4_5">4.5dp
"dp_5">5dp
"dp_6">6dp
"dp_7">7dp
"dp_8">8dp
"dp_9">9dp
"dp_10">10dp
"dp_11">11dp
"dp_12">12dp
"dp_13">13dp
"dp_14">14dp
"dp_15">15dp
"dp_16">16dp
"dp_17">17dp
"dp_18">18dp
"dp_19">19dp
"dp_20">20dp
"dp_21">21dp
"dp_22">22dp
"dp_23">23dp
"dp_24">24dp
"dp_25">25dp
"dp_26">26dp
"dp_27">27dp
"dp_28">28dp
"dp_29">29dp
"dp_30">30dp
"dp_31">31dp
"dp_32">32dp
"dp_33">33dp
"dp_34">34dp
"dp_35">35dp
"dp_36">36dp
"dp_37">37dp
"dp_38">38dp
"dp_39">39dp
"dp_40">40dp
"dp_41">41dp
"dp_42">42dp
"dp_43">43dp
"dp_44">44dp
"dp_45">45dp
"dp_46">46dp
"dp_47">47dp
"dp_48">48dp
"dp_49">49dp
"dp_50">50dp
"dp_51">51dp
"dp_52">52dp
"dp_53">53dp
"dp_54">54dp
"dp_55">55dp
"dp_56">56dp
"dp_57">57dp
"dp_58">58dp
"dp_59">59dp
"dp_60">60dp
"dp_61">61dp
"dp_62">62dp
"dp_63">63dp
"dp_64">64dp
"dp_65">65dp
"dp_66">66dp
"dp_67">67dp
"dp_68">68dp
"dp_69">69dp
"dp_70">70dp
"dp_71">71dp
"dp_72">72dp
"dp_73">73dp
"dp_74">74dp
"dp_75">75dp
"dp_76">76dp
"dp_77">77dp
"dp_78">78dp
"dp_79">79dp
"dp_80">80dp
"dp_81">81dp
"dp_82">82dp
"dp_83">83dp
"dp_84">84dp
"dp_85">85dp
"dp_86">86dp
"dp_87">87dp
"dp_88">88dp
"dp_89">89dp
"dp_90">90dp
"dp_91">91dp
"dp_92">92dp
"dp_93">93dp
"dp_94">94dp
"dp_95">95dp
"dp_96">96dp
"dp_97">97dp
"dp_98">98dp
"dp_99">99dp
"dp_100">100dp
"dp_101">101dp
"dp_102">102dp
"dp_103">103dp
"dp_104">104dp
"dp_105">105dp
"dp_106">106dp
"dp_107">107dp
"dp_108">108dp
"dp_109">109dp
"dp_110">110dp
"dp_111">111dp
"dp_112">112dp
"dp_113">113dp
"dp_114">114dp
"dp_115">115dp
"dp_116">116dp
"dp_117">117dp
"dp_118">118dp
"dp_119">119dp
"dp_120">120dp
"dp_121">121dp
"dp_122">122dp
"dp_123">123dp
"dp_124">124dp
"dp_125">125dp
"dp_126">126dp
"dp_127">127dp
"dp_128">128dp
"dp_129">129dp
"dp_130">130dp
"dp_131">131dp
"dp_132">132dp
"dp_133">133dp
"dp_134">134dp
"dp_135">135dp
"dp_136">136dp
"dp_137">137dp
"dp_138">138dp
"dp_139">139dp
"dp_140">140dp
"dp_141">141dp
"dp_142">142dp
"dp_143">143dp
"dp_144">144dp
"dp_145">145dp
"dp_146">146dp
"dp_147">147dp
"dp_148">148dp
"dp_149">149dp
"dp_150">150dp
"dp_151">151dp
"dp_152">152dp
"dp_153">153dp
"dp_154">154dp
"dp_155">155dp
"dp_156">156dp
"dp_157">157dp
"dp_158">158dp
"dp_159">159dp
"dp_160">160dp
"dp_161">161dp
"dp_162">162dp
"dp_163">163dp
"dp_164">164dp
"dp_165">165dp
"dp_166">166dp
"dp_167">167dp
"dp_168">168dp
"dp_169">169dp
"dp_170">170dp
"dp_171">171dp
"dp_172">172dp
"dp_173">173dp
"dp_174">174dp
"dp_175">175dp
"dp_176">176dp
"dp_177">177dp
"dp_178">178dp
"dp_179">179dp
"dp_180">180dp
"dp_181">181dp
"dp_182">182dp
"dp_183">183dp
"dp_184">184dp
"dp_185">185dp
"dp_186">186dp
"dp_187">187dp
"dp_188">188dp
"dp_189">189dp
"dp_190">190dp
"dp_191">191dp
"dp_192">192dp
"dp_193">193dp
"dp_194">194dp
"dp_195">195dp
"dp_196">196dp
"dp_197">197dp
"dp_198">198dp
"dp_199">199dp
"dp_200">200dp
"dp_201">201dp
"dp_202">202dp
"dp_203">203dp
"dp_204">204dp
"dp_205">205dp
"dp_206">206dp
"dp_207">207dp
"dp_208">208dp
"dp_209">209dp
"dp_210">210dp
"dp_211">211dp
"dp_212">212dp
"dp_213">213dp
"dp_214">214dp
"dp_215">215dp
"dp_216">216dp
"dp_217">217dp
"dp_218">218dp
"dp_219">219dp
"dp_220">220dp
"dp_221">221dp
"dp_222">222dp
"dp_223">223dp
"dp_224">224dp
"dp_225">225dp
"dp_226">226dp
"dp_227">227dp
"dp_228">228dp
"dp_229">229dp
"dp_230">230dp
"dp_231">231dp
"dp_232">232dp
"dp_233">233dp
"dp_234">234dp
"dp_235">235dp
"dp_236">236dp
"dp_237">237dp
"dp_238">238dp
"dp_239">239dp
"dp_240">240dp
"dp_241">241dp
"dp_242">242dp
"dp_243">243dp
"dp_244">244dp
"dp_245">245dp
"dp_246">246dp
"dp_247">247dp
"dp_248">248dp
"dp_249">249dp
"dp_250">250dp
"dp_251">251dp
"dp_252">252dp
"dp_253">253dp
"dp_254">254dp
"dp_255">255dp
"dp_256">256dp
"dp_257">257dp
"dp_258">258dp
"dp_259">259dp
"dp_260">260dp
"dp_261">261dp
"dp_262">262dp
"dp_263">263dp
"dp_264">264dp
"dp_265">265dp
"dp_266">266dp
"dp_267">267dp
"dp_268">268dp
"dp_269">269dp
"dp_270">270dp
"dp_271">271dp
"dp_272">272dp
"dp_273">273dp
"dp_274">274dp
"dp_275">275dp
"dp_276">276dp
"dp_277">277dp
"dp_278">278dp
"dp_279">279dp
"dp_280">280dp
"dp_281">281dp
"dp_282">282dp
"dp_283">283dp
"dp_284">284dp
"dp_285">285dp
"dp_286">286dp
"dp_287">287dp
"dp_288">288dp
"dp_289">289dp
"dp_290">290dp
"dp_291">291dp
"dp_292">292dp
"dp_293">293dp
"dp_294">294dp
"dp_295">295dp
"dp_296">296dp
"dp_297">297dp
"dp_298">298dp
"dp_299">299dp
"dp_300">300dp
"dp_301">301dp
"dp_302">302dp
"dp_303">303dp
"dp_304">304dp
"dp_305">305dp
"dp_306">306dp
"dp_307">307dp
"dp_308">308dp
"dp_309">309dp
"dp_310">310dp
"dp_311">311dp
"dp_312">312dp
"dp_313">313dp
"dp_314">314dp
"dp_315">315dp
"dp_316">316dp
"dp_317">317dp
"dp_318">318dp
"dp_319">319dp
"dp_320">320dp
"dp_321">321dp
"dp_322">322dp
"dp_323">323dp
"dp_324">324dp
"dp_325">325dp
"dp_326">326dp
"dp_327">327dp
"dp_328">328dp
"dp_329">329dp
"dp_330">330dp
"dp_331">331dp
"dp_332">332dp
"dp_333">333dp
"dp_334">334dp
"dp_335">335dp
"dp_336">336dp
"dp_337">337dp
"dp_338">338dp
"dp_339">339dp
"dp_340">340dp
"dp_341">341dp
"dp_342">342dp
"dp_343">343dp
"dp_344">344dp
"dp_345">345dp
"dp_346">346dp
"dp_347">347dp
"dp_348">348dp
"dp_349">349dp
"dp_350">350dp
"dp_351">351dp
"dp_352">352dp
"dp_353">353dp
"dp_354">354dp
"dp_355">355dp
"dp_356">356dp
"dp_357">357dp
"dp_358">358dp
"dp_359">359dp
"dp_360">360dp
"dp_365">365dp
"dp_370">370dp
"dp_400">400dp
"dp_410">410dp
"dp_422">422dp
"dp_472">472dp
"dp_500">500dp
"dp_600">600dp
"dp_640">640dp
"dp_720">720dp
"sp_6">6sp
"sp_7">7sp
"sp_8">8sp
"sp_9">9sp
"sp_10">10sp
"sp_11">11sp
"sp_12">12sp
"sp_13">13sp
"sp_14">14sp
"sp_15">15sp
"sp_16">16sp
"sp_17">17sp
"sp_18">18sp
"sp_19">19sp
"sp_20">20sp
"sp_21">21sp
"sp_22">22sp
"sp_23">23sp
"sp_24">24sp
"sp_25">25sp
"sp_26">26sp
"sp_27">27sp
"sp_28">28sp
"sp_29">29sp
"sp_30">30sp
"sp_31">31sp
"sp_32">32sp
"sp_33">33sp
"sp_34">34sp
"sp_35">35sp
"sp_36">36sp
"sp_37">37sp
"sp_38">38sp
"sp_40">40sp
"sp_42">42sp
"sp_48">48sp
弹窗页面的布局如下:
"1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/transparent"
android:orientation="vertical">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/dp_8"
android:background="@mipmap/icon_add_bg_9"
android:orientation="vertical"
android:paddingBottom="@dimen/dp_20"
android:paddingTop="@dimen/dp_20">
android:id="@+id/tv_change_city"
android:gravity="center"
android:layout_width="@dimen/dp_140"
android:layout_height="@dimen/dp_48"
android:text="切换城市"
android:foreground="@drawable/bg_white"
android:textColor="@color/black"
android:textSize="@dimen/sp_16"/>
android:id="@+id/tv_change_bg"
android:gravity="center"
android:layout_width="@dimen/dp_140"
android:layout_height="@dimen/dp_48"
android:text="切换背景"
android:foreground="@drawable/bg_white"
android:textColor="@color/black"
android:textSize="@dimen/sp_16"/>
android:id="@+id/tv_more"
android:gravity="center"
android:layout_width="@dimen/dp_140"
android:layout_height="@dimen/dp_48"
android:text="更多功能"
android:foreground="@drawable/bg_white"
android:textColor="@color/black"
android:textSize="@dimen/sp_16"/>
然后是对activity_main.xml文件的修改
这里修改了原来的id和src里面的图片,增加了点击的效果
icon_add.png
selector_bg_img.xml,点击之后背景变色,增加用户体验
"1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android">
- android:state_pressed="true">
android:color="#22000000"/>
-
android:color="#00000000"/>
MainActivity.java
这里我是把原来的id注释掉,不过我没有删掉,因为我要让你们知道是怎么样一个过程,你们是可以直接替换的,不替换会报错了,当然你不改Id就不会报错,但是id的命名和现在是意思对不上,会对其他人造成困扰,严谨一点就修改ID。
更改点击之后的弹窗。
要显示弹窗一些基本的配置必不可少,这里用到了一个动画工具类AnimationUtil,代码如下:
package com.llw.mvplibrary.utils;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
/**
* 动画工具类
* UpdateListener: 动画过程中通过添加此监听来回调数据
* EndListener: 动画结束的时候通过此监听器来做一些处理
*/
public class AnimationUtil {
private ValueAnimator valueAnimator;
private UpdateListener updateListener;
private EndListener endListener;
private long duration;
private float start;
private float end;
private Interpolator interpolator = new LinearInterpolator();
public AnimationUtil() {
duration = 1000; //默认动画时常1s
start = 0.0f;
end = 1.0f;
interpolator = new LinearInterpolator();// 匀速的插值器
}
public void setDuration(int timeLength) {
duration = timeLength;
}
public void setValueAnimator(float start, float end, long duration) {
this.start = start;
this.end = end;
this.duration = duration;
}
public void setInterpolator(Interpolator interpolator) {
this.interpolator = interpolator;
}
public void startAnimator() {
if (valueAnimator != null){
valueAnimator = null;
}
valueAnimator = ValueAnimator.ofFloat(start, end);
valueAnimator.setDuration(duration);
valueAnimator.setInterpolator(interpolator);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
if (updateListener == null) {
return;
}
float cur = (float) valueAnimator.getAnimatedValue();
updateListener.progress(cur);
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
if(endListener == null){
return;
}
endListener.endUpdate(animator);
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
valueAnimator.start();
}
public void addUpdateListener(UpdateListener updateListener) {
this.updateListener = updateListener;
}
public void addEndListner(EndListener endListener){
this.endListener = endListener;
}
public interface EndListener {
void endUpdate(Animator animator);
}
public interface UpdateListener {
void progress(float progress);
}
}
然后写三个方法,一个显示弹窗,及控制里面的点击事件、计算动画时间、第三个修改背景的透明度类似蒙版的效果。
这三个方法的代码我都会贴上来。不过首先,先增加弹窗出现和关闭的动画效果。
这张图告诉你在什么地方添加这个样式
pop_add_show.xml 显示动画
"1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android">
android:duration="500"
android:fromAlpha="0.0"
android:toAlpha="1.0"/>
android:duration="500"
android:fromXScale="0"
android:fromYScale="0"
android:interpolator="@android:anim/decelerate_interpolator"
android:pivotX="85%"
android:pivotY="0%"
android:toXScale="1.0"
android:toYScale="1.0"/>
pop_add_hide.xml 隐藏动画
"1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android">
android:duration="500"
android:fromAlpha="1.0"
android:toAlpha="0.0"/>
android:duration="500"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:pivotX="85%"
android:pivotY="0%"
android:toXScale="0"
android:toYScale="0"/>
然后贴一下三个方法的代码:
showAddWindow方法
/**
* 更多功能弹窗,因为区别于我原先写的弹窗
*/
private void showAddWindow() {
// 设置布局文件
mPopupWindow.setContentView(LayoutInflater.from(this).inflate(R.layout.window_add, null));// 为了避免部分机型不显示,我们需要重新设置一下宽高
mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
mPopupWindow.setBackgroundDrawable(new ColorDrawable(0x0000));// 设置pop透明效果
mPopupWindow.setAnimationStyle(R.style.pop_add);// 设置pop出入动画
mPopupWindow.setFocusable(true);// 设置pop获取焦点,如果为false点击返回按钮会退出当前Activity,如果pop中有Editor的话,focusable必须要为true
mPopupWindow.setTouchable(true);// 设置pop可点击,为false点击事件无效,默认为true
mPopupWindow.setOutsideTouchable(true);// 设置点击pop外侧消失,默认为false;在focusable为true时点击外侧始终消失
mPopupWindow.showAsDropDown(ivAdd, -100, 0);// 相对于 + 号正下面,同时可以设置偏移量
// 设置pop关闭监听,用于改变背景透明度
mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
//关闭弹窗
@Override
public void onDismiss() {
toggleBright();
}
});
//绑定布局中的控件
TextView changeCity = mPopupWindow.getContentView().findViewById(R.id.tv_change_city);
TextView changeBg = mPopupWindow.getContentView().findViewById(R.id.tv_change_bg);
TextView more = mPopupWindow.getContentView().findViewById(R.id.tv_more);
changeCity.setOnClickListener(view -> {
//切换城市
showCityWindow();
mPopupWindow.dismiss();
});
changeBg.setOnClickListener(view -> {
//切换背景
ToastUtils.showShortToast(context,"你点击了切换背景");
mPopupWindow.dismiss();
});
more.setOnClickListener(view -> {
//更多功能
ToastUtils.showShortToast(context,"如果你有什么好的建议,可以博客留言哦!");
mPopupWindow.dismiss();
});
}
计算动画时间
/**
* 计算动画时间
*/
private void toggleBright() {
// 三个参数分别为:起始值 结束值 时长,那么整个动画回调过来的值就是从0.5f--1f的
animUtil.setValueAnimator(START_ALPHA, END_ALPHA, DURATION);
animUtil.addUpdateListener(new AnimationUtil.UpdateListener() {
@Override
public void progress(float progress) {
// 此处系统会根据上述三个值,计算每次回调的值是多少,我们根据这个值来改变透明度
bgAlpha = bright ? progress : (START_ALPHA + END_ALPHA - progress);
backgroundAlpha(bgAlpha);
}
});
animUtil.addEndListner(new AnimationUtil.EndListener() {
@Override
public void endUpdate(Animator animator) {
// 在一次动画结束的时候,翻转状态
bright = !bright;
}
});
animUtil.startAnimator();
}
此方法用于改变背景的透明度
/**
* 此方法用于改变背景的透明度,从而达到“变暗”的效果
*/
private void backgroundAlpha(float bgAlpha) {
WindowManager.LayoutParams lp = getWindow().getAttributes();
// 0.0-1.0
lp.alpha = bgAlpha;
getWindow().setAttributes(lp);
// everything behind this window will be dimmed.
// 此方法用来设置浮动层,防止部分手机变暗无效
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
}
这几个方法借鉴了网络上的代码。
效果图如下:
如果你在写的过程中遇到任何问题,可以直接评论或者给我发邮件。
接下来就是切换背景了
切换背景的业务代码,我当然不可能也在MainActivity中写,因为现在里面的代码已经够多了,所以就要新建一个页面。在项目的包下新建一个ui包,用于存放除MainActivity之外的所有Activity。这样会比较规范,至于为什么不把MainActivity也放进去,因为目前我还不想放进去。
鼠标右键点击ui → New → Activity → Empty Activity
Next即可
创建好之后修改布局。
修改布局之前,这个要更改一个开关按钮的样式。
首先是增加样式代码:
"SwitchButton">
"sb_shadow_radius" format="reference|dimension"/>
"sb_shadow_offset" format="reference|dimension"/>
"sb_shadow_color" format="reference|color"/>
"sb_uncheck_color" format="reference|color"/>
"sb_checked_color" format="reference|color"/>
"sb_border_width" format="reference|dimension"/>
"sb_checkline_color" format="reference|color"/>
"sb_checkline_width" format="reference|dimension"/>
"sb_uncheckcircle_color" format="reference|color"/>
"sb_uncheckcircle_width" format="reference|dimension"/>
"sb_uncheckcircle_radius" format="reference|dimension"/>
"sb_checked" format="reference|boolean"/>
"sb_shadow_effect" format="reference|boolean"/>
"sb_effect_duration" format="reference|integer"/>
"sb_button_color" format="reference|color"/>
"sb_show_indicator" format="reference|boolean"/>
"sb_background" format="reference|color"/>
"sb_enable_effect" format="reference|boolean"/>
然后自定义View
SwitchButton.java代码如下:
package com.llw.mvplibrary.view;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Checkable;
import com.llw.mvplibrary.R;
/**
* SwitchButton.
*/
public class SwitchButton extends View implements Checkable {
private static final int DEFAULT_WIDTH = dp2pxInt(58);
private static final int DEFAULT_HEIGHT = dp2pxInt(36);
/**
* 动画状态:
* 1.静止
* 2.进入拖动
* 3.处于拖动
* 4.拖动-复位
* 5.拖动-切换
* 6.点击切换
* **/
private final int ANIMATE_STATE_NONE = 0;
private final int ANIMATE_STATE_PENDING_DRAG = 1;
private final int ANIMATE_STATE_DRAGING = 2;
private final int ANIMATE_STATE_PENDING_RESET = 3;
private final int ANIMATE_STATE_PENDING_SETTLE = 4;
private final int ANIMATE_STATE_SWITCH = 5;
public SwitchButton(Context context) {
super(context);
init(context, null);
}
public SwitchButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public SwitchButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SwitchButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
@Override
public final void setPadding(int left, int top, int right, int bottom) {
super.setPadding(0, 0, 0, 0);
}
/**
* 初始化参数
*/
private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = null;
if(attrs != null){
typedArray = context.obtainStyledAttributes(attrs, R.styleable.SwitchButton);
}
shadowEffect = optBoolean(typedArray,
R.styleable.SwitchButton_sb_shadow_effect,
true);
uncheckCircleColor = optColor(typedArray,
R.styleable.SwitchButton_sb_uncheckcircle_color,
0XffAAAAAA);//0XffAAAAAA;
uncheckCircleWidth = optPixelSize(typedArray,
R.styleable.SwitchButton_sb_uncheckcircle_width,
dp2pxInt(1.5f));//dp2pxInt(1.5f);
uncheckCircleOffsetX = dp2px(10);
uncheckCircleRadius = optPixelSize(typedArray,
R.styleable.SwitchButton_sb_uncheckcircle_radius,
dp2px(4));//dp2px(4);
checkedLineOffsetX = dp2px(4);
checkedLineOffsetY = dp2px(4);
shadowRadius = optPixelSize(typedArray,
R.styleable.SwitchButton_sb_shadow_radius,
dp2pxInt(2.5f));//dp2pxInt(2.5f);
shadowOffset = optPixelSize(typedArray,
R.styleable.SwitchButton_sb_shadow_offset,
dp2pxInt(1.5f));//dp2pxInt(1.5f);
shadowColor = optColor(typedArray,
R.styleable.SwitchButton_sb_shadow_color,
0X33000000);//0X33000000;
uncheckColor = optColor(typedArray,
R.styleable.SwitchButton_sb_uncheck_color,
0XffDDDDDD);//0XffDDDDDD;
checkedColor = optColor(typedArray,
R.styleable.SwitchButton_sb_checked_color,
0Xff51d367);//0Xff51d367;
borderWidth = optPixelSize(typedArray,
R.styleable.SwitchButton_sb_border_width,
dp2pxInt(1));//dp2pxInt(1);
checkLineColor = optColor(typedArray,
R.styleable.SwitchButton_sb_checkline_color,
Color.WHITE);//Color.WHITE;
checkLineWidth = optPixelSize(typedArray,
R.styleable.SwitchButton_sb_checkline_width,
dp2pxInt(1f));//dp2pxInt(1.0f);
checkLineLength = dp2px(6);
int buttonColor = optColor(typedArray,
R.styleable.SwitchButton_sb_button_color,
Color.WHITE);//Color.WHITE;
int effectDuration = optInt(typedArray,
R.styleable.SwitchButton_sb_effect_duration,
300);//300;
isChecked = optBoolean(typedArray,
R.styleable.SwitchButton_sb_checked,
false);
showIndicator = optBoolean(typedArray,
R.styleable.SwitchButton_sb_show_indicator,
true);
background = optColor(typedArray,
R.styleable.SwitchButton_sb_background,
Color.WHITE);//Color.WHITE;
enableEffect = optBoolean(typedArray,
R.styleable.SwitchButton_sb_enable_effect,
true);
if(typedArray != null){
typedArray.recycle();
}
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
buttonPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
buttonPaint.setColor(buttonColor);
if(shadowEffect){
buttonPaint.setShadowLayer(
shadowRadius,
0, shadowOffset,
shadowColor);
}
viewState = new ViewState();
beforeState = new ViewState();
afterState = new ViewState();
valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.setDuration(effectDuration);
valueAnimator.setRepeatCount(0);
valueAnimator.addUpdateListener(animatorUpdateListener);
valueAnimator.addListener(animatorListener);
super.setClickable(true);
this.setPadding(0, 0, 0, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if(widthMode == MeasureSpec.UNSPECIFIED
|| widthMode == MeasureSpec.AT_MOST){
widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_WIDTH, MeasureSpec.EXACTLY);
}
if(heightMode == MeasureSpec.UNSPECIFIED
|| heightMode == MeasureSpec.AT_MOST){
heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_HEIGHT, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
float viewPadding = Math.max(shadowRadius + shadowOffset, borderWidth);
height = h - viewPadding - viewPadding;
width = w - viewPadding - viewPadding;
viewRadius = height * .5f;
buttonRadius = viewRadius - borderWidth;
left = viewPadding;
top = viewPadding;
right = w - viewPadding;
bottom = h - viewPadding;
centerX = (left + right) * .5f;
centerY = (top + bottom) * .5f;
buttonMinX = left + viewRadius;
buttonMaxX = right - viewRadius;
if(isChecked()){
setCheckedViewState(viewState);
}else{
setUncheckViewState(viewState);
}
isUiInited = true;
postInvalidate();
}
/**
* @param viewState
*/
private void setUncheckViewState(ViewState viewState){
viewState.radius = 0;
viewState.checkStateColor = uncheckColor;
viewState.checkedLineColor = Color.TRANSPARENT;
viewState.buttonX = buttonMinX;
}
/**
* @param viewState
*/
private void setCheckedViewState(ViewState viewState){
viewState.radius = viewRadius;
viewState.checkStateColor = checkedColor;
viewState.checkedLineColor = checkLineColor;
viewState.buttonX = buttonMaxX;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setStrokeWidth(borderWidth);
paint.setStyle(Paint.Style.FILL);
//绘制白色背景
paint.setColor(background);
drawRoundRect(canvas,
left, top, right, bottom,
viewRadius, paint);
//绘制关闭状态的边框
paint.setStyle(Paint.Style.STROKE);
paint.setColor(uncheckColor);
drawRoundRect(canvas,
left, top, right, bottom,
viewRadius, paint);
//绘制小圆圈
if(showIndicator){
drawUncheckIndicator(canvas);
}
//绘制开启背景色
float des = viewState.radius * .5f;//[0-backgroundRadius*0.5f]
paint.setStyle(Paint.Style.STROKE);
paint.setColor(viewState.checkStateColor);
paint.setStrokeWidth(borderWidth + des * 2f);
drawRoundRect(canvas,
left + des, top + des, right - des, bottom - des,
viewRadius, paint);
//绘制按钮左边绿色长条遮挡
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(1);
drawArc(canvas,
left, top,
left + 2 * viewRadius, top + 2 * viewRadius,
90, 180, paint);
canvas.drawRect(
left + viewRadius, top,
viewState.buttonX, top + 2 * viewRadius,
paint);
//绘制小线条
if(showIndicator){
drawCheckedIndicator(canvas);
}
//绘制按钮
drawButton(canvas, viewState.buttonX, centerY);
}
/**
* 绘制选中状态指示器
* @param canvas
*/
protected void drawCheckedIndicator(Canvas canvas) {
drawCheckedIndicator(canvas,
viewState.checkedLineColor,
checkLineWidth,
left + viewRadius - checkedLineOffsetX, centerY - checkLineLength,
left + viewRadius - checkedLineOffsetY, centerY + checkLineLength,
paint);
}
/**
* 绘制选中状态指示器
* @param canvas
* @param color
* @param lineWidth
* @param sx
* @param sy
* @param ex
* @param ey
* @param paint
*/
protected void drawCheckedIndicator(Canvas canvas,
int color,
float lineWidth,
float sx, float sy, float ex, float ey,
Paint paint) {
paint.setStyle(Paint.Style.STROKE);
paint.setColor(color);
paint.setStrokeWidth(lineWidth);
canvas.drawLine(
sx, sy, ex, ey,
paint);
}
/**
* 绘制关闭状态指示器
* @param canvas
*/
private void drawUncheckIndicator(Canvas canvas) {
drawUncheckIndicator(canvas,
uncheckCircleColor,
uncheckCircleWidth,
right - uncheckCircleOffsetX, centerY,
uncheckCircleRadius,
paint);
}
/**
* 绘制关闭状态指示器
* @param canvas
* @param color
* @param lineWidth
* @param centerX
* @param centerY
* @param radius
* @param paint
*/
protected void drawUncheckIndicator(Canvas canvas,
int color,
float lineWidth,
float centerX, float centerY,
float radius,
Paint paint) {
paint.setStyle(Paint.Style.STROKE);
paint.setColor(color);
paint.setStrokeWidth(lineWidth);
canvas.drawCircle(centerX, centerY, radius, paint);
}
/**
* @param canvas
* @param left
* @param top
* @param right
* @param bottom
* @param startAngle
* @param sweepAngle
* @param paint
*/
private void drawArc(Canvas canvas,
float left, float top,
float right, float bottom,
float startAngle, float sweepAngle,
Paint paint){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc(left, top, right, bottom,
startAngle, sweepAngle, true, paint);
}else{
rect.set(left, top, right, bottom);
canvas.drawArc(rect,
startAngle, sweepAngle, true, paint);
}
}
/**
* @param canvas
* @param left
* @param top
* @param right
* @param bottom
* @param backgroundRadius
* @param paint
*/
private void drawRoundRect(Canvas canvas,
float left, float top,
float right, float bottom,
float backgroundRadius,
Paint paint){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawRoundRect(left, top, right, bottom,
backgroundRadius, backgroundRadius, paint);
}else{
rect.set(left, top, right, bottom);
canvas.drawRoundRect(rect,
backgroundRadius, backgroundRadius, paint);
}
}
/**
* @param canvas
* @param x px
* @param y px
*/
private void drawButton(Canvas canvas, float x, float y) {
canvas.drawCircle(x, y, buttonRadius, buttonPaint);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(1);
paint.setColor(0XffDDDDDD);
canvas.drawCircle(x, y, buttonRadius, paint);
}
@Override
public void setChecked(boolean checked) {
if(checked == isChecked()){
postInvalidate();
return;
}
toggle(enableEffect, false);
}
@Override
public boolean isChecked() {
return isChecked;
}
@Override
public void toggle() {
toggle(true);
}
/**
* 切换状态
* @param animate
*/
public void toggle(boolean animate) {
toggle(animate, true);
}
private void toggle(boolean animate, boolean broadcast) {
if(!isEnabled()){
return;}
if(isEventBroadcast){
throw new RuntimeException("should NOT switch the state in method: [onCheckedChanged]!");
}
if(!isUiInited){
isChecked = !isChecked;
if(broadcast){
broadcastEvent();
}
return;
}
if(valueAnimator.isRunning()){
valueAnimator.cancel();
}
if(!enableEffect || !animate){
isChecked = !isChecked;
if(isChecked()){
setCheckedViewState(viewState);
}else{
setUncheckViewState(viewState);
}
postInvalidate();
if(broadcast){
broadcastEvent();
}
return;
}
animateState = ANIMATE_STATE_SWITCH;
beforeState.copy(viewState);
if(isChecked()){
//切换到unchecked
setUncheckViewState(afterState);
}else{
setCheckedViewState(afterState);
}
valueAnimator.start();
}
/**
*
*/
private void broadcastEvent() {
if(onCheckedChangeListener != null){
isEventBroadcast = true;
onCheckedChangeListener.onCheckedChanged(this, isChecked());
}
isEventBroadcast = false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(!isEnabled()){
return false;}
int actionMasked = event.getActionMasked();
switch (actionMasked){
case MotionEvent.ACTION_DOWN:{
isTouchingDown = true;
touchDownTime = System.currentTimeMillis();
//取消准备进入拖动状态
removeCallbacks(postPendingDrag);
//预设100ms进入拖动状态
postDelayed(postPendingDrag, 100);
break;
}
case MotionEvent.ACTION_MOVE:{
float eventX = event.getX();
if(isPendingDragState()){
//在准备进入拖动状态过程中,可以拖动按钮位置
float fraction = eventX / getWidth();
fraction = Math.max(0f, Math.min(1f, fraction));
viewState.buttonX = buttonMinX
+ (buttonMaxX - buttonMinX)
* fraction;
}else if(isDragState()){
//拖动按钮位置,同时改变对应的背景颜色
float fraction = eventX / getWidth();
fraction = Math.max(0f, Math.min(1f, fraction));
viewState.buttonX = buttonMinX
+ (buttonMaxX - buttonMinX)
* fraction;
viewState.checkStateColor = (int) argbEvaluator.evaluate(
fraction,
uncheckColor,
checkedColor
);
postInvalidate();
}
break;
}
case MotionEvent.ACTION_UP:{
isTouchingDown = false;
//取消准备进入拖动状态
removeCallbacks(postPendingDrag);
if(System.currentTimeMillis() - touchDownTime <= 300){
//点击时间小于300ms,认为是点击操作
toggle();
}else if(isDragState()){
//在拖动状态,计算按钮位置,设置是否切换状态
float eventX = event.getX();
float fraction = eventX / getWidth();
fraction = Math.max(0f, Math.min(1f, fraction));
boolean newCheck = fraction > .5f;
if(newCheck == isChecked()){
pendingCancelDragState();
}else{
isChecked = newCheck;
pendingSettleState();
}
}else if(isPendingDragState()){
//在准备进入拖动状态过程中,取消之,复位
pendingCancelDragState();
}
break;
}
case MotionEvent.ACTION_CANCEL:{
isTouchingDown = false;
removeCallbacks(postPendingDrag);
if(isPendingDragState()
|| isDragState()){
//复位
pendingCancelDragState();
}
break;
}
}
return true;
}
/**
* 是否在动画状态
* @return
*/
private boolean isInAnimating(){
return animateState != ANIMATE_STATE_NONE;
}
/**
* 是否在进入拖动或离开拖动状态
* @return
*/
private boolean isPendingDragState(){
return animateState == ANIMATE_STATE_PENDING_DRAG
|| animateState == ANIMATE_STATE_PENDING_RESET;
}
/**
* 是否在手指拖动状态
* @return
*/
private boolean isDragState(){
return animateState == ANIMATE_STATE_DRAGING;
}
/**
* 设置是否启用阴影效果
* @param shadowEffect true.启用
*/
public void setShadowEffect(boolean shadowEffect) {
if(this.shadowEffect == shadowEffect){
return;}
this.shadowEffect = shadowEffect;
if(this.shadowEffect){
buttonPaint.setShadowLayer(
shadowRadius,
0, shadowOffset,
shadowColor);
}else{
buttonPaint.setShadowLayer(
0,
0, 0,
0);
}
}
public void setEnableEffect(boolean enable){
this.enableEffect = enable;
}
/**
* 开始进入拖动状态
*/
private void pendingDragState() {
if(isInAnimating()){
return;}
if(!isTouchingDown){
return;}
if(valueAnimator.isRunning()){
valueAnimator.cancel();
}
animateState = ANIMATE_STATE_PENDING_DRAG;
beforeState.copy(viewState);
afterState.copy(viewState);
if(isChecked()){
afterState.checkStateColor = checkedColor;
afterState.buttonX = buttonMaxX;
afterState.checkedLineColor = checkedColor;
}else{
afterState.checkStateColor = uncheckColor;
afterState.buttonX = buttonMinX;
afterState.radius = viewRadius;
}
valueAnimator.start();
}
/**
* 取消拖动状态
*/
private void pendingCancelDragState() {
if(isDragState() || isPendingDragState()){
if(valueAnimator.isRunning()){
valueAnimator.cancel();
}
animateState = ANIMATE_STATE_PENDING_RESET;
beforeState.copy(viewState);
if(isChecked()){
setCheckedViewState(afterState);
}else{
setUncheckViewState(afterState);
}
valueAnimator.start();
}
}
/**
* 动画-设置新的状态
*/
private void pendingSettleState() {
if(valueAnimator.isRunning()){
valueAnimator.cancel();
}
animateState = ANIMATE_STATE_PENDING_SETTLE;
beforeState.copy(viewState);
if(isChecked()){
setCheckedViewState(afterState);
}else{
setUncheckViewState(afterState);
}
valueAnimator.start();
}
@Override
public final void setOnClickListener(OnClickListener l) {
}
@Override
public final void setOnLongClickListener(OnLongClickListener l) {
}
public void setOnCheckedChangeListener(OnCheckedChangeListener l){
onCheckedChangeListener = l;
}
public interface OnCheckedChangeListener{
void onCheckedChanged(SwitchButton view, boolean isChecked);
}
/*******************************************************/
private static float dp2px(float dp){
Resources r = Resources.getSystem();
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
}
private static int dp2pxInt(float dp){
return (int) dp2px(dp);
}
private static int optInt(TypedArray typedArray,
int index,
int def) {
if(typedArray == null){
return def;}
return typedArray.getInt(index, def);
}
private static float optPixelSize(TypedArray typedArray,
int index,
float def) {
if(typedArray == null){
return def;}
return typedArray.getDimension(index, def);
}
private static int optPixelSize(TypedArray typedArray,
int index,
int def) {
if(typedArray == null){
return def;}
return typedArray.getDimensionPixelOffset(index, def);
}
private static int optColor(TypedArray typedArray,
int index,
int def) {
if(typedArray == null){
return def;}
return typedArray.getColor(index, def);
}
private static boolean optBoolean(TypedArray typedArray,
int index,
boolean def) {
if(typedArray == null){
return def;}
return typedArray.getBoolean(index, def);
}
/*******************************************************/
/**
* 阴影半径
*/
private int shadowRadius;
/**
* 阴影Y偏移px
*/
private int shadowOffset;
/**
* 阴影颜色
*/
private int shadowColor ;
/**
* 背景半径
*/
private float viewRadius;
/**
* 按钮半径
*/
private float buttonRadius;
/**
* 背景高
*/
private float height ;
/**
* 背景宽
*/
private float width;
/**
* 背景位置
*/
private float left ;
private float top ;
private float right ;
private float bottom ;
private float centerX;
private float centerY;
/**
* 背景底色
*/
private int background;
/**
* 背景关闭颜色
*/
private int uncheckColor;
/**
* 背景打开颜色
*/
private int checkedColor;
/**
* 边框宽度px
*/
private int borderWidth;
/**
* 打开指示线颜色
*/
private int checkLineColor;
/**
* 打开指示线宽
*/
private int checkLineWidth;
/**
* 打开指示线长
*/
private float checkLineLength;
/**
* 关闭圆圈颜色
*/
private int uncheckCircleColor;
/**
*关闭圆圈线宽
*/
private int uncheckCircleWidth;
/**
*关闭圆圈位移X
*/
private float uncheckCircleOffsetX;
/**
*关闭圆圈半径
*/
private float uncheckCircleRadius;
/**
*打开指示线位移X
*/
private float checkedLineOffsetX;
/**
*打开指示线位移Y
*/
private float checkedLineOffsetY;
/**
* 按钮最左边
*/
private float buttonMinX;
/**
* 按钮最右边
*/
private float buttonMaxX;
/**
* 按钮画笔
*/
private Paint buttonPaint;
/**
* 背景画笔
*/
private Paint paint;
/**
* 当前状态
*/
private ViewState viewState;
private ViewState beforeState;
private ViewState afterState;
private RectF rect = new RectF();
/**
* 动画状态
*/
private int animateState = ANIMATE_STATE_NONE;
/**
*
*/
private ValueAnimator valueAnimator;
private final android.animation.ArgbEvaluator argbEvaluator
= new android.animation.ArgbEvaluator();
/**
*是否选中
*/
private boolean isChecked;
/**
* 是否启用动画
*/
private boolean enableEffect;
/**
* 是否启用阴影效果
*/
private boolean shadowEffect;
/**
* 是否显示指示器
*/
private boolean showIndicator;
/**
* 收拾是否按下
*/
private boolean isTouchingDown = false;
/**
*
*/
private boolean isUiInited = false;
/**
*
*/
private boolean isEventBroadcast = false;
private OnCheckedChangeListener onCheckedChangeListener;
/**
* 手势按下的时刻
*/
private long touchDownTime;
private Runnable postPendingDrag = new Runnable() {
@Override
public void run() {
if(!isInAnimating()){
pendingDragState();
}
}
};
private ValueAnimator.AnimatorUpdateListener animatorUpdateListener
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
switch (animateState) {
case ANIMATE_STATE_PENDING_SETTLE: {
}
case ANIMATE_STATE_PENDING_RESET: {
}
case ANIMATE_STATE_PENDING_DRAG: {
viewState.checkedLineColor = (int) argbEvaluator.evaluate(
value,
beforeState.checkedLineColor,
afterState.checkedLineColor
);
viewState.radius = beforeState.radius
+ (afterState.radius - beforeState.radius) * value;
if(animateState != ANIMATE_STATE_PENDING_DRAG){
viewState.buttonX = beforeState.buttonX
+ (afterState.buttonX - beforeState.buttonX) * value;
}
viewState.checkStateColor = (int) argbEvaluator.evaluate(
value,
beforeState.checkStateColor,
afterState.checkStateColor
);
break;
}
case ANIMATE_STATE_SWITCH: {
viewState.buttonX = beforeState.buttonX
+ (afterState.buttonX - beforeState.buttonX) * value;
float fraction = (viewState.buttonX - buttonMinX) / (buttonMaxX - buttonMinX);
viewState.checkStateColor = (int) argbEvaluator.evaluate(
fraction,
uncheckColor,
checkedColor
);
viewState.radius = fraction * viewRadius;
viewState.checkedLineColor = (int) argbEvaluator.evaluate(
fraction,
Color.TRANSPARENT,
checkLineColor
);
break;
}
default:
case ANIMATE_STATE_DRAGING: {
}
case ANIMATE_STATE_NONE: {
break;
}
}
postInvalidate();
}
};
private Animator.AnimatorListener animatorListener
= new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
switch (animateState) {
case ANIMATE_STATE_DRAGING: {
break;
}
case ANIMATE_STATE_PENDING_DRAG: {
animateState = ANIMATE_STATE_DRAGING;
viewState.checkedLineColor = Color.TRANSPARENT;
viewState.radius = viewRadius;
postInvalidate();
break;
}
case ANIMATE_STATE_PENDING_RESET: {
animateState = ANIMATE_STATE_NONE;
postInvalidate();
break;
}
case ANIMATE_STATE_PENDING_SETTLE: {
animateState = ANIMATE_STATE_NONE;
postInvalidate();
broadcastEvent();
break;
}
case ANIMATE_STATE_SWITCH: {
isChecked = !isChecked;
animateState = ANIMATE_STATE_NONE;
postInvalidate();
broadcastEvent();
break;
}
default:
case ANIMATE_STATE_NONE: {
break;
}
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
};
/*******************************************************/
/**
* 保存动画状态
* */
private static class ViewState {
/**
* 按钮x位置[buttonMinX-buttonMaxX]
*/
float buttonX;
/**
* 状态背景颜色
*/
int checkStateColor;
/**
* 选中线的颜色
*/
int checkedLineColor;
/**
* 状态背景的半径
*/
float radius;
ViewState(){
}
private void copy(ViewState source){
this.buttonX = source.buttonX;
this.checkStateColor = source.checkStateColor;
this.checkedLineColor = source.checkedLineColor;
this.radius = source.radius;
}
}
}
然后是修改activity_background_manager.xml
"1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.BackgroundManagerActivity">
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:navigationIcon="@mipmap/icon_return"
app:contentInsetLeft="@dimen/dp_16"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title=""
app:popupTheme="@style/AppTheme.PopupOverlay">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="@dimen/sp_16"
android:textColor="@color/black"
android:text="背景管理" />
android:orientation="vertical"
android:layout_marginTop="@dimen/dp_8"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:background="@color/white"
android:paddingLeft="@dimen/dp_16"
android:paddingRight="@dimen/dp_16"
android:paddingTop="@dimen/dp_8"
android:paddingBottom="@dimen/dp_8"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:text="每日一图"
android:textColor="@color/black"
android:textSize="@dimen/sp_16"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
android:id="@+id/wb_everyday"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
android:layout_width="match_parent"
android:layout_height="@dimen/dp_1"
android:background="@color/transparent"/>
android:background="@color/white"
android:paddingLeft="@dimen/dp_16"
android:paddingRight="@dimen/dp_16"
android:paddingTop="@dimen/dp_8"
android:paddingBottom="@dimen/dp_8"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:text="图片列表"
android:textColor="@color/black"
android:textSize="@dimen/sp_16"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
android:id="@+id/wb_img_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
android:layout_width="match_parent"
android:layout_height="@dimen/dp_1"
android:background="@color/transparent"/>
android:background="@color/white"
android:paddingLeft="@dimen/dp_16"
android:paddingRight="@dimen/dp_16"
android:paddingTop="@dimen/dp_8"
android:paddingBottom="@dimen/dp_8"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:text="手动定义"
android:textColor="@color/black"
android:textSize="@dimen/sp_16"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
android:id="@+id/wb_custom_"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
接下来就是BackgroundManagerActivity.java
从布局中可以看出来有三个开关按钮,但是只能开一个,并且开启这个的同时要关闭其他的按钮,所以这个有一个相互的监听事件,并且监听事件里面要对这个按钮的是否开启做一个结果存储,这里会用到缓存,缓存是在项目全局中适用的,所以可以跨页面,比如我在这个页面里面开启了每日一图的按钮,存储值为true,然后在回到MainActivity中时,取出这个缓存里面true,然后根据这个值启用每日一图,达到更换壁纸的目的,这是最简单的操作,至于本地更换背景就比较麻烦一点,会需要先在项目的drawable里面先放几张图片,然后通过弹窗来展示这个图片,点击某一个背景则切换成功,然后弹窗关闭,这时候你的图片列表对应的按钮才会开启,加入你打开弹窗之后并没有选中任何图片,则关闭弹窗的同时也会关闭这个按钮,最后一个手动定义,就是打开手机里面图库,任你选择图片,选择之后立即提交上去,这里要先进行动态权限的申请。同意之后打开相册,好了,目前是这样的。
然后开始写代码吧
首先创建两个java类
SPUtils.java
package com.llw.goodweather.utils;
import android.content.Context;
import android.content.SharedPreferences;
/**
* sharepref工具类
*/
public class SPUtils {
private static final String NAME="config";
public static void putBoolean(String key, boolean value, Context ctx) {
SharedPreferences sp = ctx.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
sp.edit().putBoolean(key, value).commit();
}
public static boolean getBoolean(String key, boolean defValue, Context ctx) {
SharedPreferences sp = ctx.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
return sp.getBoolean(key, defValue);
}
public static void putString(String key, String value, Context ctx) {
SharedPreferences sp = ctx.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
sp.edit().putString(key, value).commit();
}
public static String getString(String key, String defValue, Context ctx) {
if(ctx!=null){
SharedPreferences sp = ctx.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
return sp.getString(key, defValue);
}
return "";
}
public static void putInt(String key, int value, Context ctx) {
SharedPreferences sp = ctx.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
sp.edit().putInt(key, value).commit();
}
public static int getInt(String key, int defValue, Context ctx) {
SharedPreferences sp = ctx.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
return sp.getInt(key, defValue);
}
public static void remove(String key, Context ctx) {
SharedPreferences sp = ctx.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
sp.edit().remove(key).commit();
}
}
Constant.java
package com.llw.goodweather.utils;
/**
* 统一管理缓存中对应的KEY
*/
public class Constant {
public final static int SUCCESS_CODE = 200;
public final static String CITY = "city";//市
public final static String DISTRICT = "district";//区/县
public final static String EVERYDAY_IMG = "everyday_img";//每日图片开关
public final static String IMG_LIST = "img_list";//图片列表开关
public final static String CUSTOM_IMG = "custom_img";//手动定义开关
}
接下来修改mvplibrary中的BaseActivity.java
private static final int FAST_CLICK_DELAY_TIME = 500;
private static long lastClickTime;
//返回
public void Back(Toolbar toolbar){
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
context.finish();
if(!isFastClick()) {
context.finish();
}
}
});
}
// 两次点击间隔不能少于500ms
public static boolean isFastClick() {
boolean flag = true;
long currentClickTime = System.currentTimeMillis();
if ((currentClickTime - lastClickTime) >= FAST_CLICK_DELAY_TIME ) {
flag = false;
}
lastClickTime = currentClickTime;
return flag;
}
在这里写好之后,到时候在Activity中直接调用即可。然后回到BackgroundManagerActivity
继承BaseActivity之后就可以用它里面的一些方法了。
initSwitchButton方法
//初始化三个开关按钮 三个只能开一个
private void initSwitchButton() {
//每日一图按钮开关监听
wbEveryday.setOnCheckedChangeListener(new SwitchButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(SwitchButton view, boolean isChecked) {
if(isChecked){
//开
SPUtils.putBoolean(Constant.EVERYDAY_IMG,true,context);
wbImgList.setChecked(false);
wbCustom.setChecked(false);
}else {
//关
SPUtils.putBoolean(Constant.EVERYDAY_IMG,false,context);
}
}
});
//图片列表按钮开关监听
wbImgList.setOnCheckedChangeListener(new SwitchButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(SwitchButton view, boolean isChecked) {
if(isChecked){
SPUtils.putBoolean(Constant.IMG_LIST,true,context);
wbEveryday.setChecked(false);
wbCustom.setChecked(false);
}else {
SPUtils.putBoolean(Constant.IMG_LIST,false,context);
}
}
});
//手动定义按钮开关监听
wbCustom.setOnCheckedChangeListener(new SwitchButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(SwitchButton view, boolean isChecked) {
if(isChecked){
SPUtils.putBoolean(Constant.CUSTOM_IMG,true,context);
wbEveryday.setChecked(false);
wbImgList.setChecked(false);
}else {
SPUtils.putBoolean(Constant.CUSTOM_IMG,false,context);
}
}
});
}
然后在MainActivity中指定点击切换背景是页面跳转
现在你就可以运行一下了,一般情况下是不会报错的,运行出来的结果应该是,三个开关按钮,一开始就是默认都没有打开,但是你也只能开一个,开另一个的时候会关闭这个,你可以试一下。
每日一图的比较简单,只要缓存里面放了这个值就行了。
麻烦的是后面这两个
首先是六张本地壁纸,放在drawable里,这里我还是贴一下这些壁纸吧
img_1.jpg
img_2.jpg
img_3.jpg
img_4.jpg
img_5.jpg
img_6.jpg
然后创建一个样式文件,用于图片选中后的样式。
现在colors.xml里面增加两个颜色
"green">#77d034
"transparent_bg_white">#22FFFFFF
"1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android">
- android:drawable="@drawable/check_normal_rb" android:state_checked="false"/>
- android:drawable="@drawable/check_checked_rb" android:state_checked="true"/>
check_normal_rb.xml
"1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android">
android:width="1dip" android:color="@color/transparent" />
check_checked_rb.xml
"1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android">
android:color="@color/transparent_bg_white" />
android:width="2dip" android:color="@color/green" />
然后在mvplibrary模块中的build.radle文件中的dependencies{}闭包中增加一个依赖库
//自由嵌套的RadioGroup
api 'com.github.fodroid:XRadioGroup:v1.5'
然后Sync一下,否则不会生效的。
然后在app的layout下创建一个新的弹窗布局文件window_img_list.xml
"1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="@color/white"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:id="@+id/xrg_img"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_marginRight="@dimen/dp_4"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="200dp">
android:layout_margin="5dp"
android:background="@drawable/img_1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:id="@+id/rb_img_1"
android:button="@null"
android:background="@drawable/check_style"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_marginLeft="@dimen/dp_4"
android:layout_marginRight="@dimen/dp_4"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="200dp">
android:layout_margin="5dp"
android:background="@drawable/img_2"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:id="@+id/rb_img_2"
android:button="@null"
android:background="@drawable/check_style"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_marginLeft="@dimen/dp_4"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="200dp">
android:layout_margin="5dp"
android:background="@drawable/img_3"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:id="@+id/rb_img_3"
android:button="@null"
android:background="@drawable/check_style"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_marginRight="@dimen/dp_4"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="200dp">
android:layout_margin="5dp"
android:background="@drawable/img_4"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:id="@+id/rb_img_4"
android:button="@null"
android:background="@drawable/check_style"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_marginLeft="@dimen/dp_4"
android:layout_marginRight="@dimen/dp_4"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="200dp">
android:layout_margin="5dp"
android:background="@drawable/img_5"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:id="@+id/rb_img_5"
android:button="@null"
android:background="@drawable/check_style"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_marginLeft="@dimen/dp_4"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="200dp">
android:layout_margin="5dp"
android:background="@drawable/img_6"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:id="@+id/rb_img_6"
android:button="@null"
android:background="@drawable/check_style"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
然后在BackgroundManagerActivity.java中用弹窗显示
LiWindow liWindow;//弹窗
RxPermissions rxPermissions;//权限请求
private AnimationUtil animationUtil;//动画工具类
private float bgAlpha = 1f;//背景透明度
private boolean bright = false;//判断标识
private static final long DURATION = 500;//0.5s
private static final float START_ALPHA = 0.7f;//开始透明度
private static final float END_ALPHA = 1f;//结束透明度
找次的弹窗我打算底部显示出现,里面的动画文件早就已经写好了,在前几篇就有,如果你是一步一步跟著我写过来的,那么就不会有问题,现在主要是修改LiWindow.java中的**showBottomPopupWindow()**方法,修改后的代码如下:
/**
* 底部显示
* @param mView
*/
public void showBottomPopupWindow(View mView,PopupWindow.OnDismissListener onDismissListener) {
mPopupWindow = new PopupWindow(mView,
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
mPopupWindow.setContentView(mView);
mPopupWindow.setOutsideTouchable(true);//点击空白处不关闭弹窗 true为关闭
mPopupWindow.setFocusable(true);
mPopupWindow.setAnimationStyle(R.style.AnimationBottomFade); //设置动画
mPopupWindow.showAtLocation(mView, Gravity.BOTTOM, 0, 0);
mPopupWindow.setOnDismissListener(onDismissListener);
}
主要是将里面的窗口关闭方法的监听放在里传入参数里,这样既可以在Activity中操作弹窗关闭时的事件了。
接下来是弹窗的使用
/**
* 显示图片弹窗
*/
private void showImgWindow() {
liWindow = new LiWindow(context);
final View view = LayoutInflater.from(context).inflate(R.layout.window_img_list, null);
XRadioGroup xRadioGroup = (XRadioGroup) view.findViewById(R.id.xrg_img);
//显示弹窗的时候,取缓存,判断里面有没有选中过图片
int position = SPUtils.getInt(Constant.IMG_POSITION, -1, context);
RadioButton rbImg1 = (RadioButton) view.findViewById(R.id.rb_img_1);
RadioButton rbImg2 = (RadioButton) view.findViewById(R.id.rb_img_2);
RadioButton rbImg3 = (RadioButton) view.findViewById(R.id.rb_img_3);
RadioButton rbImg4 = (RadioButton) view.findViewById(R.id.rb_img_4);
RadioButton rbImg5 = (RadioButton) view.findViewById(R.id.rb_img_5);
RadioButton rbImg6 = (RadioButton) view.findViewById(R.id.rb_img_6);
switch (position) {
case 0:
rbImg1.setChecked(true);
break;
case 1:
rbImg2.setChecked(true);
break;
case 2:
rbImg3.setChecked(true);
break;
case 3:
rbImg4.setChecked(true);
break;
case 4:
rbImg5.setChecked(true);
break;
case 5:
rbImg6.setChecked(true);
break;
}
//xRadioGroup的选中监听
xRadioGroup.setOnCheckedChangeListener(new XRadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(XRadioGroup xRadioGroup, int i) {
//得出选中id对应的RadioButton,从而知道选中的是哪一个,并放入缓存,0~5
switch (xRadioGroup.getCheckedRadioButtonId()) {
case R.id.rb_img_1:
SPUtils.putInt(Constant.IMG_POSITION, 0, context);
liWindow.closePopupWindow();
break;
case R.id.rb_img_2:
SPUtils.putInt(Constant.IMG_POSITION, 1, context);
liWindow.closePopupWindow();
break;
case R.id.rb_img_3:
SPUtils.putInt(Constant.IMG_POSITION, 2, context);
liWindow.closePopupWindow();
break;
case R.id.rb_img_4:
SPUtils.putInt(Constant.IMG_POSITION, 3, context);
liWindow.closePopupWindow();
break;
case R.id.rb_img_5:
SPUtils.putInt(Constant.IMG_POSITION, 4, context);
liWindow.closePopupWindow();
break;
case R.id.rb_img_6:
SPUtils.putInt(Constant.IMG_POSITION, 5, context);
liWindow.closePopupWindow();
break;
default:
SPUtils.putInt(Constant.IMG_POSITION, 5, context);
break;
}
ToastUtils.showShortToast(context,"已更换壁纸");
}
});
//弹窗关闭监听 弹窗关闭时,如果什么都没有选中,则自然不会有缓存中的取会0~5,所以当为-1时,关闭图片列表的开关
PopupWindow.OnDismissListener onDismissListener = new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
toggleBright();
int position = SPUtils.getInt(Constant.IMG_POSITION, -1, context);
if (position != -1) {
wbImgList.setChecked(true);
} else {
wbImgList.setChecked(false);
}
}
};
liWindow.showBottomPopupWindow(view, onDismissListener);//显示弹窗 ,传入关闭弹窗监听
}
/**
* 计算动画时间
*/
private void toggleBright() {
// 三个参数分别为:起始值 结束值 时长,那么整个动画回调过来的值就是从0.5f--1f的
animationUtil.setValueAnimator(START_ALPHA, END_ALPHA, DURATION);
animationUtil.addUpdateListener(new AnimationUtil.UpdateListener() {
@Override
public void progress(float progress) {
// 此处系统会根据上述三个值,计算每次回调的值是多少,我们根据这个值来改变透明度
bgAlpha = bright ? progress : (START_ALPHA + END_ALPHA - progress);
backgroundAlpha(bgAlpha);
}
});
animationUtil.addEndListner(new AnimationUtil.EndListener() {
@Override
public void endUpdate(Animator animator) {
// 在一次动画结束的时候,翻转状态
bright = !bright;
}
});
animationUtil.startAnimator();
}
/**
* 此方法用于改变背景的透明度,从而达到“变暗”的效果
*/
private void backgroundAlpha(float bgAlpha) {
WindowManager.LayoutParams lp = getWindow().getAttributes();
// 0.0-1.0
lp.alpha = bgAlpha;
getWindow().setAttributes(lp);
// everything behind this window will be dimmed.
// 此方法用来设置浮动层,防止部分手机变暗无效
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
}
注释都写在代码里面了,可能一开始你会看到云里雾里,不过我相信你会明白的,接下来就是这个弹窗的显示调用
到这里图片列表就写完了。你不妨运行一下
在Constant.java
红框中为新增的Key
下面的内容会用到一个相机相册的工具类,代码我贴一下:
package com.llw.goodweather.utils;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.ImageView;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
/**
* 相机、相册工具类
*/
public class CameraUtils {
public static Intent getTakePhotoIntent(Context context, File outputImagepath){
//获取系統版本
int currentapiVersion = Build.VERSION.SDK_INT;
// 激活相机
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 判断存储卡是否可以用,可用进行存储
if (hasSdcard()) {
if (currentapiVersion < 24) {
// 从文件中创建uri
Uri uri = Uri.fromFile(outputImagepath);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
} else {
//兼容android7.0 使用共享文件的形式
ContentValues contentValues = new ContentValues(1);
contentValues.put(MediaStore.Images.Media.DATA, outputImagepath.getAbsolutePath());
Uri uri = context.getApplicationContext().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
}
}
return intent;
}
public static Intent getSelectPhotoIntent(){
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
return intent;
}
/*
* 判断sdcard是否被挂载
*/
public static boolean hasSdcard() {
return Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED);
}
/**
* 4.4及以上系统处理图片的方法
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public static String getImgeOnKitKatPath(Intent data, Context context) {
String imagePath = null;
Uri uri = data.getData();
Log.d("uri=intent.getData :", "" + uri);
if (DocumentsContract.isDocumentUri(context, uri)) {
String docId = DocumentsContract.getDocumentId(uri); //数据表里指定的行
Log.d("getDocumentId(uri) :", "" + docId);
Log.d("uri.getAuthority() :", "" + uri.getAuthority());
if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
String id = docId.split(":")[1];
String selection = MediaStore.Images.Media._ID + "=" + id;
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection,context);
} else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
imagePath = getImagePath(contentUri, null,context);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
imagePath = getImagePath(uri, null,context);
}
return imagePath;
}
/**
* 通过uri和selection来获取真实的图片路径,从相册获取图片时要用
*/
public static String getImagePath(Uri uri, String selection, Context context) {
String path = null;
Cursor cursor = context.getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
//改变拍完照后图片方向不正的问题
public static void ImgUpdateDirection(String filepath, Bitmap orc_bitmap, ImageView iv) {
int digree = 0;//图片旋转的角度
//根据图片的URI获取图片的绝对路径
Log.i("tag", ">>>>>>>>>>>>>开始");
//String filepath = ImgUriDoString.getRealFilePath(getApplicationContext(), uri);
Log.i("tag", "》》》》》》》》》》》》》》》" + filepath);
//根据图片的filepath获取到一个ExifInterface的对象
ExifInterface exif = null;
try {
exif = new ExifInterface(filepath);
Log.i("tag", "exif》》》》》》》》》》》》》》》" + exif);
if (exif != null) {
// 读取图片中相机方向信息
int ori = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
// 计算旋转角度
switch (ori) {
case ExifInterface.ORIENTATION_ROTATE_90:
digree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
digree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
digree = 270;
break;
default:
digree = 0;
break;
}
}
//如果图片不为0
if (digree != 0) {
// 旋转图片
Matrix m = new Matrix();
m.postRotate(digree);
orc_bitmap = Bitmap.createBitmap(orc_bitmap, 0, 0, orc_bitmap.getWidth(),
orc_bitmap.getHeight(), m, true);
}
if (orc_bitmap != null) {
iv.setImageBitmap(orc_bitmap);
}
} catch (IOException e) {
e.printStackTrace();
exif = null;
}
}
/**
* 4.4以下系统处理图片的方法
*/
public static String getImageBeforeKitKatPath(Intent data, Context context) {
Uri uri = data.getData();
String imagePath = getImagePath(uri, null,context);
return imagePath;
}
//比例压缩
public static Bitmap comp(Bitmap image) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
if (baos.toByteArray().length / 1024 > 5120) {
//判断如果图片大于5M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
baos.reset();//重置baos即清空baos
image.compress(Bitmap.CompressFormat.JPEG, 50, baos);//这里压缩50%,把压缩后的数据存放到baos中
}
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
BitmapFactory.Options newOpts = new BitmapFactory.Options();
//开始读入图片,此时把options.inJustDecodeBounds 设回true了
newOpts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
//现在主流手机比较多是800*480分辨率,所以高和宽我们设置为
float hh = 800f;//这里设置高度为800f
float ww = 480f;//这里设置宽度为480f
//缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
int be = 1;//be=1表示不缩放
if (w > h && w > ww) {
//如果宽度大的话根据宽度固定大小缩放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {
//如果高度高的话根据宽度固定大小缩放
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
be = 1;
newOpts.inSampleSize = be;//设置缩放比例
newOpts.inPreferredConfig = Bitmap.Config.RGB_565;//降低图片从ARGB888到RGB565
//重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
isBm = new ByteArrayInputStream(baos.toByteArray());
bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
return bitmap;//压缩好比例大小后再进行质量压缩
}
}
手动定义,顾名思义,就是打开本地相册,所以首先得版本判断,然后是权限请求,打开相册,返回图片的路径。
//权限判断
private void permissionVersion() {
Intent intent = new Intent();
if (Build.VERSION.SDK_INT >= 23) {
//6.0或6.0以上
//动态权限申请
permissionsRequest();
} else {
//6.0以下
//发现只要权限在AndroidManifest.xml中注册过,均会认为该权限granted 提示一下即可
if (Build.VERSION.SDK_INT <19) {
intent.setAction(Intent.ACTION_GET_CONTENT);
}else {
intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
}
startActivityForResult(intent, SELECT_PHOTO);
}
}
//动态权限申请
private void permissionsRequest() {
//使用这个框架需要制定JDK版本,建议用1.8
rxPermissions = new RxPermissions(context);
rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.subscribe(granted -> {
if (granted) {
//申请成功
//得到权限之后打开本地相册
Intent selectPhotoIntent = CameraUtils.getSelectPhotoIntent();
startActivityForResult(selectPhotoIntent, SELECT_PHOTO);
} else {
//申请失败
wbCustom.setChecked(false);
ToastUtils.showShortToast(this, "权限未开启");
}
});
}
返回结果中获取图片地址,然后放入缓存中
/**
* Activity返回结果
* @param requestCode
* @param resultCode
* @param data
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
//打开相册后返回
case SELECT_PHOTO:
if (resultCode == RESULT_OK) {
String imagePath = null;
//判断手机系统版本号
if (Build.VERSION.SDK_INT > 19) {
//4.4及以上系统使用这个方法处理图片
imagePath = CameraUtils.getImgeOnKitKatPath(data, this);
} else {
imagePath = CameraUtils.getImageBeforeKitKatPath(data, this);
}
displayImage(imagePath);
}
break;
default:
break;
}
}
/**
* 从相册获取完图片(根据图片路径显示图片)
*/
private void displayImage(String imagePath) {
if (!TextUtils.isEmpty(imagePath)) {
//将本地上传选中的图片地址放入缓存,当手动定义开关打开时,取出缓存中的图片地址,显示为背景
SPUtils.putString(Constant.CUSTOM_IMG_PATH, imagePath, context);
ToastUtils.showShortToast(context,"已更换为你选择的壁纸");
} else {
wbCustom.setChecked(true);//关闭手动定义开关
ToastUtils.showShortToast(context,"图片获取失败");
}
}
boolean isEverydayImg = SPUtils.getBoolean(Constant.EVERYDAY_IMG, false, context);//每日图片
boolean isImgList = SPUtils.getBoolean(Constant.IMG_LIST, false, context);//图片列表
boolean isCustomImg = SPUtils.getBoolean(Constant.CUSTOM_IMG, false, context);//手动定义
if (isEverydayImg == true) {
wbEveryday.setChecked(true);
} else if (isImgList == true) {
wbImgList.setChecked(true);
} else if (isCustomImg == true) {
wbCustom.setChecked(true);
}
完成这一步之后,这个BackgroundManagerActivity.java的代码就写完了,但具体的功能还没有完成,因为MainActivity才是显示你这个背景的地方啊。
回来MainActivity.java
这里要聊一下关于Actiivty的生命周期的问题,但是鉴于本篇博客的内容和字数已经够多了,所以我放一个链接Activity生命周期,你可以去看看这个生命周期,否则我后面的内容你就不是很能理解为什么。
然后在跳转壁纸管理页面的时候增加当前页面区/县、市的缓存,当在回到MainActivity时,取出缓存中的值,然后进行接口请求。
@Override
protected void onResume() {
super.onResume();
showLoadingDialog();//在数据请求之前放在加载等待弹窗,返回结果后关闭弹窗
if(district == null){
//取出缓存
district = SPUtils.getString(Constant.DISTRICT,"",context);
city = SPUtils.getString(Constant.CITY,"",context);
}
isOpenChangeBg();//是否开启了切换背景
//获取今天的天气数据
mPresent.todayWeather(context, district);
//获取天气预报数据
mPresent.weatherForecast(context, district);
//获取生活指数数据
mPresent.lifeStyle(context, district);
//获取逐小时天气数据
mPresent.hourly(context, district);
//获取空气质量数据
mPresent.airNowCity(context, city);
}
//判断是否开启了切换背景,没有开启则用默认的背景
private void isOpenChangeBg() {
boolean isEverydayImg = SPUtils.getBoolean(Constant.EVERYDAY_IMG,false,context);//每日图片
boolean isImgList = SPUtils.getBoolean(Constant.IMG_LIST,false,context);//图片列表
boolean isCustomImg = SPUtils.getBoolean(Constant.CUSTOM_IMG,false,context);//手动定义
//因为只有有一个为true,其他两个就都会是false,所以可以一个一个的判断
if(isEverydayImg != true && isImgList != true && isCustomImg != true){
//当所有开关都没有打开的时候用默认的图片
bg.setBackgroundResource(R.drawable.pic_bg);
}else {
if(isEverydayImg!=false){
//开启每日一图
mPresent.biying(context);
}else if(isImgList!=false){
//开启图片列表
int position = SPUtils.getInt(Constant.IMG_POSITION,-1,context);
switch (position){
case 0:
bg.setBackgroundResource(R.drawable.img_1);
break;
case 1:
bg.setBackgroundResource(R.drawable.img_2);
break;
case 2:
bg.setBackgroundResource(R.drawable.img_3);
break;
case 3:
bg.setBackgroundResource(R.drawable.img_4);
break;
case 4:
bg.setBackgroundResource(R.drawable.img_5);
break;
case 5:
bg.setBackgroundResource(R.drawable.img_6);
break;
}
}else if(isCustomImg != false){
String imgPath = SPUtils.getString(Constant.CUSTOM_IMG_PATH,"",context);
Glide.with(context)
.asBitmap()
.load(imgPath)
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
Drawable drawable = new BitmapDrawable(context.getResources(), resource);
bg.setBackground(drawable);
}
});
}
}
}
到这一步,页面就算是真正的写完了。说实话写博客是一个耗费脑力和体力的活,思路很重要啊。
下一篇:Android 天气APP(十四)修复UI显示异常、优化业务代码逻辑、增加详情天气显示