qq空间装扮怎么自定义背景透明自定义

Android(13)
前面已经实现过仿QQ的List抽屉效果以及仿QQ未读消息拖拽效果,具体请见:
趁热打铁,这次我们实现QQ空间的主页全效果,先贴上我们最终的完成效果图:
可以看到,我们实现了如下效果:
1. 下拉拖拽视差效果
2. 透明状态栏+TitleBar
3. 状态栏+TitleBar颜色动态渐变
4. 下拉加载更多
5. 点击按钮∨弹出PopupWindow list选项+模糊背景效果
6. 点击按钮+顶部弹出PopupWindow界面+模糊背景效果
下拉拖拽视差效果
第一步先实现拖拽视差效果,也就是下拉的时候,有一种阻滞感,然后手抬起的时候,会稍微回弹一下。
在实现效果之前,我们先看一下实现原理,我们看一下下面这张图:
实际上呢,一整个视差效果界面,其实就是一个ListView。我们给listView设置了一个headView,然后设置headView 布局的scaleType为centerCrop,取src图片的中部也就是图中绿色部分,这部分是初始显示区域。headView的src图片上下部分实际上是处于界面之外没有显示出来,也就是图中的棕色部分。
下面贴上头布局代码:
list_item_head.xml
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"&
android:layout_width="match_parent"
android:layout_height="330dp"
android:scaleType="centerCrop"
android:id="@+id/iv_head"
android:src="@mipmap/parallax_img"/&
然后在Activity中为listView添加头布局:
private void init() {
for (int i = 0
list.add("user - " + i)
lvParallax = (ListView) findViewById(R.id.lv_parallax)
lvParallax.setOverScrollMode(ListView.OVER_SCROLL_NEVER)
View headerView = View.inflate(this, R.layout.list_item_head, null)
ImageView ivHead = (ImageView) headerView.findViewById(R.id.iv_head)
lvParallax.initParallaxImageParams(ivHead)
lvParallax.addHeaderView(headerView)
lvParallax.setAdapter(new ParallaxAdapter(this, list))
item布局代码就不贴了,我们看看现在运行的效果:
注:为了方便截图,后面的图都是运行在模拟器(480x800)上的效果截图,所以显示效果肯定跟最开始的真机(720x1280)效果有一定的区别,不过此处只是做演示,这点小事就先忽略啦~ =。=
既然布局已经完成了,那么我们接下来实现视差拖拽效果。
既然要拖拽,我们肯定要自定义一个ListView并且重写其onTouchEvent以及overScrollBy方法。
首先我们要思考的是,我们如何在自定义控件中拿到我们headView的高度以及图片的高度呢?由于我们的headView参数是在Activity的onCreate中初始化的,但是在onCreate中无法通过getHeight()和getWidth()拿到headView的高度和宽度,因为View组件布局要在onResume回调后完成。那么我们如何在onCreate中拿到headView的高度参数呢?这里我们通过getViewTreeObserver().addOnGlobalLayoutListener()来获得宽度或者高度。这是获得一个view的宽度和高度的方法之一。
OnGlobalLayoutListener 是ViewTreeObserver的内部类,当一个视图树的布局发生改变时,可以被ViewTreeObserver监听到,这是一个注册监听视图树的观察者(observer),在视图树的全局事件改变时得到通知。ViewTreeObserver不能直接实例化,而是通过getViewTreeObserver()获得。
不多说,上代码:
* 初始化ParallaxImage的初始参数
* imageView
public void initParallaxImageParams(final ImageView imageView) {
this.ivHead = imageV
imageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver
.OnGlobalLayoutListener() {
public void onGlobalLayout() {
imageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
orignalHeight = imageView.getHeight();
int drawbleHeight = imageView.getDrawable().getIntrinsicHeight();
maxHeight = orignalHeight & drawbleHeight ? orignalHeight * 2 : drawbleH
在onGlobalLayout中增加一层判断,当headView初始高度大于图片高度时,我们取的上下滑动最大高度是headView*2。因为从根本上来讲,我们肯定是要保证headView上下部分肯定是超出界面之外的,所以这里的maxHeight肯定是要大于headView的高度的。
然后重写overScrollBy方法,overScrollBy会在listview滑动到头的时候执行,可以获取到继续滑动的距离和方向。当滑动到头的时候,我们通过继续滚动的距离,动态设置headView的高度,这样达到一个拖动显示的效果。
* 在listview滑动到头的时候执行,可以获取到继续滑动的距离和方向
* deltaX:继续滑动x方向的距离
* deltaY:继续滑动y方向的距离
负:表示顶部到头
正:表示底部到头
* maxOverScrollX:x方向最大可以滚动的距离
* maxOverScrollY:y方向最大可以滚动的距离
* isTouchEvent: true: 是手指拖动滑动
false:表示fling靠惯性滑动;
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int
scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean
isTouchEvent) {
if (deltaY & 0 && isTouchEvent) {
if (ivHead != null) {
int newHeight = ivHead.getHeight() - deltaY / 3;
if (newHeight & maxHeight) {
newHeight = maxH
ivHead.getLayoutParams().height = newH
ivHead.requestLayout();
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
maxOverScrollX, maxOverScrollY, isTouchEvent);
最后重写onTouchEvent方法,在这里检测手抬起动作,在手抬起的时候通过一个属性动画回复headView原本的高度:
public boolean onTouchEvent(MotionEvent ev) {
if (MotionEventCompat.getActionMasked(ev) == MotionEvent.ACTION_UP) {
final ValueAnimator animator = ValueAnimator.ofInt(ivHead.getHeight(), orignalHeight);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int animateValue = (int) animator.getAnimatedValue();
ivHead.getLayoutParams().height = animateV
ivHead.requestLayout();
animator.setInterpolator(new OvershootInterpolator(3.f));
animator.setDuration(350);
animator.start();
return super.onTouchEvent(ev);
最后的视差拖拽效果实现如下:
透明状态栏+TitleBar
视差拖拽效果实现完成,当然离我们最终要的漂漂的效果还有距离,距离在哪呢,首先我们没有TitleBar,再接着呢,这个状态栏,也太丑了!!!
下面首先实现透明状态栏
在Activity setContentView(R.layout.activity_main)之后,我们执行下面的代码,要注意的是setStatusBarColor这个方法,也就是设置状态栏颜色的方法,是API21也就是5.0以后才有的方法,在5.0之前是无法实现的,不过现在7.0都出来了,5.0之前的机型应该也不多了。
* 初始化状态栏状态
* 设置Activity状态栏透明效果
* 隐藏ActionBar
private void initState() {
UIUtils.setBarColor(this, Color.TRANSPARENT);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
* 设置状态栏背景色
* 4.4以下不处理
* 4.4使用默认沉浸式状态栏
public static void setBarColor(Activity activity, int color) {
if (Build.VERSION.SDK_INT &= Build.VERSION_CODES.KITKAT) {
Window win = activity.getWindow();
View decorView = win.getDecorView();
win.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
if (Build.VERSION.SDK_INT &= Build.VERSION_CODES.LOLLIPOP) {
win.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | option);
win.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
win.setStatusBarColor(color);
辣么我们现在的效果如何呢?
嚯,已经成功把状态栏变透明了。
接下来看看TitleBar,由于我们实际上是将整个应用沾满整个屏幕,也就是说App应用主体实际上占用了状态栏的空间并且状态栏背景设置成了透明,所以实现了现在这种应用作为状态栏背景的效果。在应用没有占据全屏的情况下,布局应该是从状态栏之下开始布局的,但是现在应用实际上是从屏幕(0,0)开始布局的,所以在实际应用中,TitleBar的高度应该是设置为状态栏高度+原本期望TitleBar的高度。
下面贴上TitleBar代码
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tb_title"
android:layout_width="match_parent"
android:layout_height="90dp"&
android:id="@+id/btn_back"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:background="@mipmap/back"/&
android:id="@+id/rl_title"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_marginTop="43dp"
android:layout_weight="1"&
android:id="@+id/iv_title"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_toLeftOf="@+id/tv_title"
android:layout_marginRight="5dp"
android:layout_marginTop="2dp"
android:src="@mipmap/refesh"
android:visibility="invisible"/&
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="80dp"
android:background="@android:color/transparent"
android:text="Title"
android:layout_centerInParent="true"
android:textColor="@android:color/white"
android:textSize="20sp"/&
android:id="@+id/btn_add"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:background="@mipmap/add_white"/&
最后将TitleBar和ListView放在一个FrameLayout中,界面上的布局,基本完成。
状态栏+TitleBar颜色动态渐变
基本界面已经实现完成,接下来我们看看怎么实现状态栏和TitleBar颜色渐变。前面我们说了,TitleBar和ListView是放在一个FrameLayout中的。所以思路应该很明确了,就是在这个FrameLayout中动态的设置TitleBar的背景色,由于状态栏实际是透明背景然后被TitleBar充满的,所以实际上我们这里说的状态栏+TitleBar颜色动态渐变其实单修改TitleBar的背景色就可以了。
首先我们实现一个自定义GradientLayout ,在GradientLayout中 给ParallaxListView设置一个OnScrollListener ,将根据ParallaxListView滑动的距离和预设值求出一个fraction值,然后根据fraction和估值器计算出颜色值并且设置给TitleBar达到动态更新TitleBar和状态栏颜色的效果。
由于TitlBar右上角的添加按钮需要根据滑动距离更新背景,所以这里我们增加一个接口OnGradientStateChangeListenr ,TitleBar实现这个接口,然后根据GradientLayout传过去的fraction值以及关键值来更新按钮”+”的状态:
public class GradientLayout extends FrameLayout implements OnScrollListener {
private TitleBar tb_
private ParallaxListV
private static final float CRITICAL_VALUE = 0.5f;
private OnGradientStateChangeListenr onGradientStateChangeL
* 设置Gradient状态监听
* onGradientStateChangeListenr
public void setOnGradientStateChangeListenr(OnGradientStateChangeListenr onGradientStateChangeListenr){
this.onGradientStateChangeListenr = onGradientStateChangeL
public GradientLayout(Context context) {
this(context, null);
public GradientLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
public GradientLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context =
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() != 2) {
throw new IllegalArgumentException("only can 2 child in this view");
if (getChildAt(0) instanceof ParallaxListView) {
plv = (ParallaxListView) getChildAt(0);
plv.setOnScrollListener(this);
throw new IllegalArgumentException("child(0) must be ParallaxListView");
tb_title = (TitleBar) getChildAt(1);
tb_title.setTitleBarListenr(this);
* 设置title背景色
public void setTitleBackground(int color) {
tb_title.setBackgroundColor(color);
public void onScrollStateChanged(AbsListView view, int scrollState) {
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int
totalItemCount) {
if (firstVisibleItem == 0) {
View headView = view.getChildAt(0);
if (headView != null) {
float slideValue = Math.abs(headView.getTop()) - headView.getHeight() / 2.f +
tb_title.getHeight();
if (slideValue & 0)
slideValue = 0;
float fraction = slideValue / (headView.getHeight() / 2.f);
if (fraction & 1) {
fraction = 1;
excuteAnim(fraction);
float fraction = 1;
excuteAnim(fraction);
private void excuteAnim(float fraction) {
int color = (int) ColorUtil.evaluateColor(fraction, Color.parseColor("#0000ccff"), Color
.parseColor("#ff00ccff"));
setTitleBackground(color);
onGradientStateChangeListenr.onChange(fraction, CRITICAL_VALUE);
* 设置TitleBar text
public void setTitleText(String msg){
tb_title.setTitleText(msg);
* Gradient变化临界值监听
public interface OnGradientStateChangeListenr{
* 当fraction超过临界值时回调
* fraction
* criticalValue
public void onChange(float fraction, float criticalValue);
TitleBar实现OnGradientStateChangeListenr
* 设置Gradient临界值监听
public void setTitleBarListenr(GradientLayout gl) {
gl.setOnGradientStateChangeListenr(new OnGradientStateChangeListenr() {
public void onChange(float fraction, float criticalValue) {
* 当变化值超过临界值
if (fraction &= criticalValue) {
btn_add.setBackgroundResource(R.mipmap.add_trans);
btn_add.setBackgroundResource(R.mipmap.add_white);
至此我们的效果如下:
下拉加载更多
感觉现在基本已经像一个比较靠谱的demo了,现在继续增加下拉加载更多的功能。其实有了前面的铺垫,下拉加载实现起来其实非常简单。
首先在ParallaxListView监听下拉拖拽的距离,然后在松手的时候根据拖拽距离计算出是否出发加载更多,最后通过接口回调的方式将这个下拉刷新的状态以及结果通知给GradientLayout,GradientLayout又通过接口回调的方式通知TitleBar更新界面。不多说,直接上代码,要注意的一点是,为了独立开ParallaxListView和TitleBar,ParallaxListView和TitleBar的状态更新全部通过父Layout GradientLayout。
ParallaxListView增加刷新接口以及模拟请求数据
public boolean onTouchEvent(MotionEvent ev) {
if (MotionEventCompat.getActionMasked(ev) == MotionEvent.ACTION_UP) {
if (ivHead.getHeight() - orignalHeight & 60) {
if(onRefeshChangeListener != null){
onRefeshChangeListener.onListRefesh();
if(!isRefeshing){
getData();
isRefeshing = true;
final ValueAnimator animator = ValueAnimator.ofInt(ivHead.getHeight(), orignalHeight);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int animateValue = (int) animator.getAnimatedValue();
ivHead.getLayoutParams().height = animateV
ivHead.requestLayout();
animator.setInterpolator(new OvershootInterpolator(3.f));
animator.setDuration(350);
animator.start();
return super.onTouchEvent(ev);
* 开启一个线程模拟网络请求操作
private void getData(){
new Thread(new Runnable() {
public void run() {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
onRefeshChangeListener.onListRefeshFinish(true);
isRefeshing = false;
}).start();
GradientLayout实现ParallaxListView.OnRefeshChangeListener并且新增一个OnRefeshChangeListener接口用于将状态给TitleBar,实际上GradientLayout相当于ParallaxListView和TitleBar的传话者。
public void onListRefesh() {
onRefeshChangeListener.onListRefesh();
public void onListRefeshFinish(final boolean isRefeshSuccess) {
UIUtils.runOnUIThread(new Runnable() {
public void run() {
if(isRefeshSuccess){
Toast.makeText(UIUtils.getContext(), "refesh failed.", Toast.LENGTH_SHORT).show();
onRefeshChangeListener.onListRefeshFinish();
* GradientLayout中的子list列表刷新状态监听
public interface OnRefeshChangeListener{
* 开始刷新列表,请求数据
void onListRefesh();
* 刷新列表完成
void onListRefeshFinish();
TitleBar实现父Layout的接口,然后通过一个Tween动画实现刷新进度圈圈的旋转:
* 设置TitleBar监听
public void setTitleBarListenr(GradientLayout gl) {
gl.setOnGradientStateChangeListenr(new OnGradientStateChangeListenr() {
public void onChange(float fraction, float criticalValue) {
* 当变化值超过临界值
if (fraction &= criticalValue) {
btn_add.setBackgroundResource(R.mipmap.add_trans);
btn_add.setBackgroundResource(R.mipmap.add_white);
gl.setOnRefeshChangeListener(new GradientLayout.OnRefeshChangeListener() {
public void onListRefesh() {
UIUtils.runOnUIThread(new Runnable() {
public void run() {
iv_title.setVisibility(View.VISIBLE);
Animation anim = AnimationUtils.loadAnimation(context, R.anim.refesh_roate);
anim.setInterpolator(new LinearInterpolator());
iv_title.startAnimation(anim);
public void onListRefeshFinish() {
UIUtils.runOnUIThread(new Runnable() {
public void run() {
iv_title.setVisibility(View.INVISIBLE);
iv_title.clearAnimation();
现在再看看我们的效果,泪流满面,终于实现大部分效果了!
点击按钮∨弹出PopupWindow list选项+模糊背景效果
接下来要实现的是QQ空间好友动态列表选项弹出的效果,QQ是弹出一个屏幕等宽的列表。我们这里实现的稍微跟QQ的有点不一样,我们这里实现的效果更像是3D touch的效果。
先来撸一撸思路,既然是弹出来,首相第一个想到的实现方法,当然是PopupWindow,然后背景虚化,其实网上也有很多的模糊虚化方法,然后再接着就是将我们要添加的View设到屏幕上。OK,思路很清晰简单,然鹅,真的辣么简单吗?
并没有啊!!!一开始就出点了小意外,就是关于WindowManager.LayoutParams,由于这玩意的flag值实在是太多了,网上这类功能相关的资料又比较少,最后好一番折腾,总算是实现了我们要的效果,也就是虚化背景不满全屏,但是不知道为什么,模拟器状态栏依然显示的是半透明状态栏,好在真机上运行都一切正常,然后就妥妥的无视模拟器这个问题了。
先看看我们配置的WindowManager.LayoutParams,这里只列出来我们用到的几个flag值,折腾了小半天,最后也就用到这么几个,委屈的不行,哈哈。
private void initParams() {
params = new WindowManager.LayoutParams();
params.width = MATCH_PARENT;
params.height = MATCH_PARENT;
params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
params.format = PixelFormat.TRANSLUCENT;
params.gravity = Gravity.LEFT | Gravity.TOP;
接下来要思考的是将listView塞进一个空layout,这个地方要注意的是,由于我们这里弹出的listView背景是一个.9图片,所以一定要记住将这个.9图片设置个listView做背景!!!而不是设置给我们的空layout!!!
由于listView宽度我们希望是自适应而不是充满屏幕,所以我们要自定义一个listView,并且根据item的最大宽度设置listView的宽度,下面贴上自定义listView的代码。
public class PopupListView extends ListView {
public PopupListView(Context context) {
this(context, null);
public PopupListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
public PopupListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context =
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int maxWidth = measureWidthByChilds() + getPaddingLeft() + getPaddingRight();
super.onMeasure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.UNSPECIFIED),
heightMeasureSpec);
public int measureWidthByChilds() {
int maxWidth = 0;
View view = null;
for (int i = 0; i & getAdapter().getCount(); i++) {
view = getAdapter().getView(i, view, this);
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
if (view.getMeasuredWidth() & maxWidth) {
maxWidth = view.getMeasuredWidth();
view = null;
return maxW
有一点比较重要,我们在popupWindow弹出来的时候,需要拦截返回键事件,点击返回键时dismiss掉popupWindow,如何拦截返回键事件呢?我们这里通过一个自定义layout,重写这个layout的dispatchKeyEvent事件然后暴露一个接口,实际上相当于对dispatchKeyEvent事件做了一次传递,然后在popupWindow中实现setDispatchKeyEventListener的回调。
* 拦截WindowManager中view的按键事件,此处主要用于返回键事件拦截
* Created by Horrarndoo on .
public class PopupRootLayout extends FrameLayout{
private DispatchKeyEventListener mDispatchKeyEventL
public PopupRootLayout(Context context) {
super(context);
public PopupRootLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
public PopupRootLayout(Context context, AttributeSet attrs) {
super(context, attrs);
public boolean dispatchKeyEvent(KeyEvent event) {
if (mDispatchKeyEventListener != null) {
return mDispatchKeyEventListener.dispatchKeyEvent(event);
return super.dispatchKeyEvent(event);
public DispatchKeyEventListener getDispatchKeyEventListener() {
return mDispatchKeyEventL
public void setDispatchKeyEventListener(DispatchKeyEventListener mDispatchKeyEventListener) {
this.mDispatchKeyEventListener = mDispatchKeyEventL
public static interface DispatchKeyEventListener {
boolean dispatchKeyEvent(KeyEvent event);
最后贴上PopupWindow的代码,设置虚化背景和弹出/隐藏ListView都是通过属性动画,比较简单,代码注释也比较全,就不多做解释了。
public class BlurPopupWindow {
* 顶部弹出popupWindow关键字
public static final int KEYWORD_LOCATION_TOP = 1;
* 点击处弹出popupWindow关键字
public static final int KEYWORD_LOCATION_CLICK = 2;
private WindowManager.LayoutP
private boolean isD
private WindowManager windowM
private PopupRootLayout rootV
private ViewGroup contentL
private final int animDuration = 250;
private boolean isA
* BlurPopupWindow构造函数
* activity 当前弹出/消失BlurPopupWindow的Activity
要弹出/消失的view内容
默认从点击处弹出/消失popupWindow
public BlurPopupWindow(Activity activity, View view) {
initBlurPopupWindow(activity, view, KEYWORD_LOCATION_CLICK);
* BlurPopupWindow构造函数
* activity 当前弹出/消失BlurPopupWindow的Activity
要弹出/消失的view内容
弹出/消失位置关键字 KEYWORD_LOCATION_TOP:顶部弹出
KEYWORD_LOCATION_CLICK:点击位置弹出
public BlurPopupWindow(Activity activity, View view, int keyword) {
initBlurPopupWindow(activity, view, keyword);
* BlurPopupWindow初始化
* activity 当前弹出BlurPopupWindow的Activity
要弹出/消失的view内容
弹出/消失位置关键字 KEYWORD_LOCATION_TOP:顶部弹出
KEYWORD_LOCATION_CLICK:点击位置弹出
private void initBlurPopupWindow(Activity activity, View view, int keyword) {
this.activity =
windowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
switch (keyword) {
case KEYWORD_LOCATION_CLICK:
view.setPadding(5, 10, 5, 0);
view.setBackgroundResource(R.drawable.popup_bg);
case KEYWORD_LOCATION_TOP:
ImageView imageView = (ImageView)
imageView.setScaleType(ImageView.ScaleType.FIT_START);
imageView.setImageDrawable(activity.getResources().getDrawable(R.mipmap.popup_top_bg));
initLayout(view, keyword);
private void initLayout(View view, final int keyword) {
rootView = (PopupRootLayout) View.inflate(activity, R.layout.popupwindow_layout, null);
contentLayout = (ViewGroup) rootView.findViewById(R.id.content_layout);
initParams();
contentLayout.addView(view);
rootView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
dismissPopupWindow(keyword);
rootView.setDispatchKeyEventListener(new DispatchKeyEventListener() {
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (rootView.getParent() != null) {
dismissPopupWindow(keyword);
return true;
return false;
private void initParams() {
params = new WindowManager.LayoutParams();
params.width = MATCH_PARENT;
params.height = MATCH_PARENT;
params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
params.format = PixelFormat.TRANSLUCENT;
params.gravity = Gravity.LEFT | Gravity.TOP;
* 将bitmap模糊虚化并设置给view background
* 虚化后的view
private View getBlurView(View view, Bitmap bitmap) {
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth() / 3, bitmap
.getHeight() / 3, false);
Bitmap blurBitmap = UIUtils.getBlurBitmap(activity, scaledBitmap, 5);
view.setAlpha(0);
view.setBackgroundDrawable(new BitmapDrawable(null, blurBitmap));
alphaAnim(view, 0, 1, animDuration);
* 弹出选项弹窗
* 默认从点击位置弹出
* locationView
public void displayPopupWindow(View locationView) {
displayPopupWindow(locationView, KEYWORD_LOCATION_CLICK);
* 弹出选项弹窗
* locationView 被点击的view
弹出位置关键字
public void displayPopupWindow(View locationView, int keyword) {
if (!isAniming) {
isAniming = true;
int[] point = new int[2];
float x = 0;
float y = 0;
contentLayout.measure(0, 0);
switch (keyword) {
case KEYWORD_LOCATION_CLICK:
locationView.getLocationOnScreen(point);
x = point[0] + locationView.getWidth() - contentLayout.getMeasuredWidth();
y = point[1] + locationView.getHeight();
case KEYWORD_LOCATION_TOP:
contentLayout.setX(x);
contentLayout.setY(y);
View decorView = activity.getWindow().getDecorView();
Bitmap bitmap = UIUtils.viewToBitmap(decorView);
View blurView = getBlurView(rootView, bitmap);
windowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
windowManager.addView(blurView, params);
popupAnim(contentLayout, 0.f, 1.f, animDuration, keyword, true);
} catch (Exception e) {
e.printStackTrace();
* 消失popupWindow
* 默认从点击处开始消失
public void dismissPopupWindow() {
dismissPopupWindow(KEYWORD_LOCATION_CLICK);
* 消失popupWindow
消失位置关键字 KEYWORD_LOCATION_TOP:顶部弹出
KEYWORD_LOCATION_CLICK:点击位置弹出
public void dismissPopupWindow(int keyword) {
if (!isAniming) {
isAniming = true;
if (isDisplay) {
popupAnim(contentLayout, 1.f, 0.f, animDuration, keyword, false);
* 设置透明度属性动画
要执行属性动画的view
* duration 动画持续时间
private void alphaAnim(final View view, int start, int end, int duration) {
ObjectAnimator.ofFloat(view, "alpha", start, end).setDuration(duration).start();
* popupWindow属性动画
* duration
* isToDisplay 显示或消失 flag值
private void popupAnim(final View view, float start, final float end, int duration, final int
keyword, final boolean isToDisplay) {
ValueAnimator va = ValueAnimator.ofFloat(start, end).setDuration(duration);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
switch (keyword) {
case KEYWORD_LOCATION_CLICK:
view.setPivotX(view.getMeasuredWidth());
view.setPivotY(0);
view.setScaleX(value);
view.setScaleY(value);
view.setAlpha(value);
case KEYWORD_LOCATION_TOP:
view.setPivotX(0);
view.setPivotY(0);
view.setScaleY(value);
va.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
isAniming = false;
if(isToDisplay) {
isDisplay = true;
onPopupStateListener.onDisplay(isDisplay);
if (isDisplay) {
windowManager.removeViewImmediate(rootView);
} catch (Exception e) {
e.printStackTrace();
isDisplay = false;
onPopupStateListener.onDismiss(isDisplay);
va.start();
现在看看实现效果。
点击按钮+顶部弹出PopupWindow界面+模糊背景效果
接下来的是最难的一个地方!!!
并不是!!!骗你的!!!哈哈,实际上前面的代码也已经写的很清楚了,我们这个顶部弹出的这个界面是个什么东西呢?没错!!!就是一个ImageView!!!鹅已!!!
OK,玩笑开完。要注意一点就是ImageView应避免设置background而是应该设置src,因为设置background可能会因为图片比例导致图片拉伸失真,当然QQ顶部弹下来的肯定不是一个ImageView,这里也只是做一个效果,实际应用中自然可以根据需求去拓展。
最后定义一个接口OnPopupStateListener 用于将PopupWindow状态告知给TitleBar,然后TitleBar按键根据回调状态给按钮“+”设置属性动画。
* popupWindow显示和消失状态变化接口
public interface OnPopupStateListener {
* popupWindow状态变化
* isDisplay popupWindow当前状态 true:显示 false:消失
* popupWindow为显示状态
void onDisplay(boolean isDisplay);
* popupWindow为消失状态
void onDismiss(boolean isDisplay);
TitleBar 接口实现以及按钮动画
private void initPopupWindow(final Activity context) {
ImageView iv_popup_top = new ImageView(context);
LayoutParams params = new LayoutParams(LayoutParams
.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
iv_popup_top.setLayoutParams(params);
blurPopupWindow = new BlurPopupWindow(context, iv_popup_top,
KEYWORD_LOCATION_TOP);
blurPopupWindow.setOnPopupStateListener(new BlurPopupWindow.OnPopupStateListener() {
public void onDisplay(boolean isDisplay) {
TitleBar.this.isDisplay = isD
public void onDismiss(boolean isDisplay) {
TitleBar.this.isDisplay = isD
dismissAnim();
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_add:
if (onBarClicklistener != null) {
if (isDisplay) {
dismissPopupWindow();
displayPopupWindow(v);
onBarClicklistener.onBarClick(R.id.btn_add);
case R.id.btn_back:
if (onBarClicklistener != null) {
onBarClicklistener.onBarClick(R.id.btn_back);
public void displayPopupWindow(View v) {
displayAnim();
blurPopupWindow.displayPopupWindow(v, KEYWORD_LOCATION_TOP);
public void dismissPopupWindow() {
dismissAnim();
blurPopupWindow.dismissPopupWindow(KEYWORD_LOCATION_TOP);
* Add按钮逆时针转90度
private void displayAnim() {
ObjectAnimator.ofFloat(btn_add, "rotation", 0.f, -90.f).setDuration(500).start();
* Add按钮瞬时间转90度
private void dismissAnim() {
ObjectAnimator.ofFloat(btn_add, "rotation", 0.f, 90.f).setDuration(500).start();
贴上最终模拟器上运行的效果
最后附上完整demo地址:
访问:16757次
排名:千里之外
原创:22篇
(1)(1)(2)(1)(1)(1)(2)(4)(6)(1)(1)(2)}

我要回帖

更多关于 qq空间自定义图片 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信