Android 實現切換主題皮膚功能(類似于眾多app中的 夜間模式,主題包等)

首先來個最簡單的一鍵切換主題功能,就做個白天和晚上的主題好了。

先看我們的styles文件:

 1 <resources>2 3     <!-- Base application theme. -->4     <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">5         <!-- Customize your theme here. -->6         <item name="colorPrimary">@color/colorPrimary</item>7         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>8         <item name="colorAccent">@color/colorAccent</item>9     </style>
10     <style name="AppTheme.NoActionBar">
11         <item name="windowActionBar">false</item>
12         <item name="windowNoTitle">true</item>
13     </style>
14     <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
15     <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
16 
17     <!--白天主題-->
18     <style name="dayTheme" parent="AppTheme">
19           <item name="android:textColor">#525252</item>
20         <item name="android:background">#f7f7f7</item>
21     </style>
22 
23     <!--夜間主題-->
24     <style name="nightTheme" parent="AppTheme">
25         <item name="android:textColor">#868a96</item>
26         <item name="android:background">#1e1e2a </item>
27     </style>
28 
29 
30 </resources>

好,然后我們來看看主要activity

package com.example.administrator.mainchangethemeapp;import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;public class MainActivity extends AppCompatActivity {private TextView tv;@Overrideprotected void onCreate(Bundle savedInstanceState) {//sputils是對SharedPreferences的封裝,代碼就不上了,大家理解意思就行了if(SPUtils.get(this,"theme","dayTheme").equals("dayTheme")){//默認是白天主題setTheme(R.style.dayTheme);}else{//否則是晚上主題setTheme(R.style.nightTheme);}super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tv=(TextView)this.findViewById(R.id.tv);tv.setOnClickListener(new View.OnClickListener(){@Overridepublic void onClick(View v) {if(SPUtils.get(MainActivity.this,"theme","dayTheme").equals("dayTheme")){SPUtils.put(MainActivity.this,"theme","nightTheme");}else{SPUtils.put(MainActivity.this, "theme", "dayTheme");}recreate();}});}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.menu_main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();//noinspection SimplifiableIfStatementif (id == R.id.action_settings) {return true;}return super.onOptionsItemSelected(item);}
}

然后來看下效果:

?

當然了,上面這個demo實際上是有缺陷的。有人會說了 你看人家新浪微博,那么多主題,你要是用這種方法,你這個app得有多大,

能做成微博那樣要用什么就去下載什么么?答案是可以的。其實方案也很簡單。

這種方案的思路就是,把主題包 額外做成一個apk,注意這個apk 是不會在桌面顯示的。你只能在設置里的app列表里找到他。

然后在你的主activity里 取這個apk里的資源 即可。這里我也把這種方案的代碼寫一遍。注意下載apk 安裝apk的代碼我就不寫了。

我們就假設 我們要切換的主題就是系統自帶的主題就行了,我們的主activity 使用的是白天主題。然后我們的子app里面

為了簡化代碼,我們就不放自定義主題了,就使用android studio 原始的代碼,一步步來,

先放上子app里的styles文件,其實這個里面一行代碼都沒更改,全是ide自己生成的:

 1 <resources>2 3     <!-- Base application theme. -->4     <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">5         <!-- Customize your theme here. -->6         <item name="colorPrimary">@color/colorPrimary</item>7         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>8         <item name="colorAccent">@color/colorAccent</item>9     </style>
10     <style name="AppTheme.NoActionBar">
11         <item name="windowActionBar">false</item>
12         <item name="windowNoTitle">true</item>
13     </style>
14     <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
15     <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
16 
17 
18 </resources>

然后就是子activity里的 配置文件manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.administrator.sonproject" ><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:supportsRtl="true"android:theme="@style/AppTheme" ><activityandroid:name=".MainActivity"android:label="@string/app_name"android:theme="@style/AppTheme.NoActionBar" ><intent-filter><action android:name="android.intent.action.MAIN" /></intent-filter></activity></application></manifest>

注意這里?

<category android:name="android.intent.category.LAUNCHER" />

的這行代碼 我是沒寫的。這樣就是保證在這個apk 安裝好以后不會在桌面出現。
然后我們來看看主activity代碼
 1 package com.example.administrator.mainchangethemeapp;2 3 import android.content.Context;4 import android.content.SharedPreferences;5 import android.content.pm.PackageManager;6 import android.content.res.TypedArray;7 import android.os.Bundle;8 import android.support.design.widget.FloatingActionButton;9 import android.support.design.widget.Snackbar;
10 import android.support.v4.app.Fragment;
11 import android.support.v7.app.AppCompatActivity;
12 import android.support.v7.widget.Toolbar;
13 import android.util.Log;
14 import android.util.TypedValue;
15 import android.view.View;
16 import android.view.Menu;
17 import android.view.MenuItem;
18 import android.widget.TextView;
19 
20 public class MainActivity extends AppCompatActivity {
21 
22     private TextView tv;
23 
24     @Override
25     protected void onCreate(Bundle savedInstanceState) {
26         //sputils是對SharedPreferences的封裝,代碼就不上了,大家理解意思就行了
27         if (SPUtils.get(this, "theme", "dayTheme").equals("dayTheme")) {
28             //默認是白天主題
29             setTheme(R.style.dayTheme);
30         } else {
31             //否則是晚上主題,這裡晚上主題我們就去加載我們晚上主題apk里的資源
32             int resourceId = getResourceId(getPackageContext(this, "com.example.administrator.sonproject"), "style", "AppTheme");
33             setTheme(resourceId);
34         }
35         super.onCreate(savedInstanceState);
36         setContentView(R.layout.activity_main);
37         tv = (TextView) this.findViewById(R.id.tv);
38         tv.setOnClickListener(new View.OnClickListener() {
39 
40             @Override
41             public void onClick(View v) {
42                 if (SPUtils.get(MainActivity.this, "theme", "dayTheme").equals("dayTheme")) {
43                     SPUtils.put(MainActivity.this, "theme", "nightTheme");
44                 } else {
45                     SPUtils.put(MainActivity.this, "theme", "dayTheme");
46                 }
47                 recreate();
48             }
49         });
50     }
51 
52     @Override
53     public boolean onCreateOptionsMenu(Menu menu) {
54         // Inflate the menu; this adds items to the action bar if it is present.
55         getMenuInflater().inflate(R.menu.menu_main, menu);
56         return true;
57     }
58 
59     @Override
60     public boolean onOptionsItemSelected(MenuItem item) {
61         // Handle action bar item clicks here. The action bar will
62         // automatically handle clicks on the Home/Up button, so long
63         // as you specify a parent activity in AndroidManifest.xml.
64         int id = item.getItemId();
65 
66         //noinspection SimplifiableIfStatement
67         if (id == R.id.action_settings) {
68             return true;
69         }
70 
71         return super.onOptionsItemSelected(item);
72     }
73 
74 
75     /**
76      * 獲取其他apk的context
77      * @param context
78      * @param packageName
79      * @return
80      */
81     public static Context getPackageContext(Context context, String packageName) {
82         try {
83             return context.createPackageContext(packageName, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
84         } catch (PackageManager.NameNotFoundException e) {
85             e.printStackTrace();
86         }
87         return null;
88     }
89 
90     //獲取指定context的 type里面的 name屬性
91     public static int getResourceId(Context context, String type, String name) {
92         return context.getResources().getIdentifier(name, type, context.getPackageName());
93     }
94 }
 

你看,到這里 我們就實現了,動態加載主題的方式了。當然了,到這里依舊是不完美的,因為settheme方法大家都知道一定要重啟activity才有效啊。這樣就不好了。

體驗并非最佳。總是給人很突兀的感覺。而且我們都知道重啟activity的成本很大。要考慮很多生命周期之類的東西。那我們繼續往下看,看看有什么比較好的解決方案能解決這個問題。

首先 我們可以考慮一下這個問題,所謂的切換主題 之類的,無非就是把你那些控件的 背景色啊 字體顏色之類的 改變了一下。對于一個app來說,用戶一般只有一個界面 會有切換主題的這個入口,

換句話說,我們的app里面 只有這一個activity 需要實現 不重啟activity就切換 控件style的功能,其他activity我們是不需要實現這個功能的,因為再切換過去的時候基本上都會走oncreate。所以

我們只需要考慮這個 有切換主題按鈕的 這個activity能實現 不重啟activity就換皮膚的功能就可以了。其他activity不需要考慮。so 這樣一想 這個功能就簡單清晰了很多。

首先我們可以自定義2個屬性,我們把他放在attrs 這個xml里面

1 <?xml version="1.0" encoding="utf-8"?>
2 <resources>
3     <!-- 首先自定義屬性,這里我們為了簡便 只自定義了這2種屬性,如果你要自己做的話 看需求可以增加屬性-->
4     <!-- 另外注意我們這里并沒有使用declare-styleable  來包裹這里的2個屬性 好處就是在xml文件里 不用另外定義前綴了-->
5     <attr name="custom_background" format="reference|color"/>
6     <attr name="custom_textcolor" format="reference|color"/>
7 </resources>

?

然后我們去定義一下我們的主題:

 1 <resources>2 3     <!-- 這里就設置2個最簡單的 主題就行了,-->4     <!--白天主題-->5     <style name="dayTheme" >6         <item name="custom_background">#f7f7f7</item>7         <item name="custom_textcolor">#525252</item>8     </style>9 
10     <!--晚上主題-->
11     <style name="nightTheme">
12         <item name="custom_background">#1e1e2a</item>
13         <item name="custom_textcolor">#868a96</item>
14     </style>
15 </resources>

然后我們來寫一下mainactivity的xml布局文件,我們假設這個布局是非常簡單的:

 1 <?xml version="1.0" encoding="utf-8"?>2 <com.example.administrator.mainchangethemeapp.ThemeRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"3     android:layout_width="match_parent"4     android:layout_height="match_parent"5     android:background="?attr/custom_background"6     android:id="@+id/mainview"7     >8 9     <com.example.administrator.mainchangethemeapp.ThemeTextView
10         android:id="@+id/tv"
11         android:layout_width="100dp"
12         android:layout_height="100dp"
13         android:layout_centerInParent="true"
14         android:text="一鍵切換主題"
15         android:background="?attr/custom_background"
16         android:textColor="?attr/custom_textcolor"
17         />
18 
19 </com.example.administrator.mainchangethemeapp.ThemeRelativeLayout>

你看這個布局里面 就一個relativelayout和一個textview。 和以前xml唯一的區別就是這里backaground和textcolor使用的值 是我們前面定義好的屬性了(但是要注意 這屬性是沒有值的 賦值的操作放在java代碼里實現)。同時這2個控件 也并非是系統控件 而是自定義控件。然后我們看看這個自定義控件是怎么寫的。注意我這里就做了2個自定義控件,如果你們的那個切換主題的入口頁面里面有其他控件的話,就要學著下面的方法 自己拓展一下了,其實也很簡單的。

首先呢,我們來定義一個接口,誰實現了這個接口 就說明這個控件可以不啟動activity直接換膚!

 1 package com.example.administrator.mainchangethemeapp;2 3 import android.content.res.Resources;4 import android.view.View;5 6 /**7  * Created by Administrator on 2015/11/14.8  */9 public interface ThemeUIInterface {
10 
11     public View getView();
12     public  void setTheme(Resources.Theme themeId);
13 }

然后我們來看看2個自定義控件怎么寫:

 1 package com.example.administrator.mainchangethemeapp;2 3 import android.content.Context;4 import android.content.res.Resources;5 import android.util.AttributeSet;6 import android.util.Log;7 import android.view.View;8 import android.widget.RelativeLayout;9 
10 /**
11  * Created by Administrator on 2015/11/14.
12  */
13 public class ThemeRelativeLayout extends RelativeLayout implements ThemeUIInterface{
14 
15     private int attr_background = -1;
16 
17     public ThemeRelativeLayout(Context context) {
18         super(context);
19     }
20 
21     public ThemeRelativeLayout(Context context, AttributeSet attrs) {
22         super(context, attrs);
23         this.attr_background =ViewAttributeUtil.getBackgroundAttibute(attrs);
24     }
25 
26     public ThemeRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
27         super(context, attrs, defStyleAttr);
28         this.attr_background =ViewAttributeUtil.getBackgroundAttibute(attrs);
29 
30     }
31 
32 
33     @Override
34     public View getView() {
35         return this;
36     }
37 
38     @Override
39     public void setTheme(Resources.Theme themeId) {
40         if(attr_background!=-1) {
41             ViewAttributeUtil.applyBackgroundDrawable(this, themeId, attr_background);
42         }
43     }
44 }
 1 package com.example.administrator.mainchangethemeapp;2 3 import android.content.Context;4 import android.content.res.Resources;5 import android.util.AttributeSet;6 import android.view.View;7 import android.widget.TextView;8 9 /**
10  * Created by Administrator on 2015/11/16.
11  */
12 public class ThemeTextView extends TextView implements ThemeUIInterface{
13 
14     private int attr_drawable=-1;
15     private int attr_textColor=-1;
16 
17     public ThemeTextView(Context context) {
18         super(context);
19     }
20 
21     public ThemeTextView(Context context, AttributeSet attrs) {
22         super(context, attrs);
23         this.attr_drawable = ViewAttributeUtil.getBackgroundAttibute(attrs);
24         this.attr_textColor = ViewAttributeUtil.getTextColorAttribute(attrs);
25     }
26 
27     public ThemeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
28         super(context, attrs, defStyleAttr);
29         this.attr_drawable = ViewAttributeUtil.getBackgroundAttibute(attrs);
30         this.attr_textColor = ViewAttributeUtil.getTextColorAttribute(attrs);
31     }
32 
33     @Override
34     public View getView() {
35         return this;
36     }
37 
38     @Override
39     public void setTheme(Resources.Theme themeId) {
40         if (attr_drawable != -1) {
41             ViewAttributeUtil.applyBackgroundDrawable(this, themeId, attr_drawable);
42         }
43         if (attr_textColor != -1) {
44             ViewAttributeUtil.applyTextColor(this, themeId, attr_textColor);
45         }
46     }
47 }

看上去 其實也蠻簡單的對吧,無非就相比傳統控件,他對外暴露了 setTheme這個方法罷了,而這個setTheme方法 其實就做了一件事,調用系統自己的方法重新set那些屬性罷了。

 1 package com.example.administrator.mainchangethemeapp;2 3 4 import android.content.res.Resources;5 import android.content.res.TypedArray;6 import android.graphics.drawable.Drawable;7 import android.util.AttributeSet;8 import android.util.Log;9 import android.widget.ImageView;
10 import android.widget.TextView;
11 
12 public class ViewAttributeUtil {
13 
14     public static int getAttributeValue(AttributeSet attr, int paramInt) {
15         int value = -1;
16         int count = attr.getAttributeCount();
17         for(int i = 0; i <count;i++) {
18             if(attr.getAttributeNameResource(i) == paramInt) {
19                 String str = attr.getAttributeValue(i);
20                 if(null != str && str.startsWith("?")) {
21                     value = Integer.valueOf(str.substring(1, str.length())).intValue();
22                     return value;
23                 }
24             }
25         }
26         return value;
27     }
28 
29     public static int getBackgroundAttibute(AttributeSet attr) {
30         return getAttributeValue(attr , android.R.attr.background);
31     }
32 
33 
34     public static int getTextColorAttribute(AttributeSet attr) {
35         return getAttributeValue(attr, android.R.attr.textColor);
36     }
37 
38     public static void applyBackgroundDrawable(ThemeUIInterface ci, Resources.Theme theme, int paramInt) {
39         TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
40         Drawable drawable = ta.getDrawable(0);
41         if(null != ci) {
42             (ci.getView()).setBackgroundDrawable(drawable);
43         }
44         ta.recycle();
45     }
46     public static void applyTextColor(ThemeUIInterface ci, Resources.Theme theme, int paramInt) {
47         TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
48         int resourceId = ta.getColor(0,0);
49         if(null != ci && ci instanceof TextView) {
50             ((TextView)ci.getView()).setTextColor(resourceId);
51         }
52         ta.recycle();
53     }
54 
55 }

好,到這里 脈絡就逐漸清晰了,我們就看看主activity里怎么寫了:

 1 package com.example.administrator.mainchangethemeapp;2 3 import android.app.Activity;4 import android.os.Bundle;5 import android.view.Menu;6 import android.view.MenuItem;7 import android.view.View;8 import android.widget.TextView;9 
10 public class MainActivity extends Activity {
11 
12     private TextView tv;
13 
14     private View view;
15 
16     @Override
17     protected void onCreate(Bundle savedInstanceState) {
18         //這行代碼千萬不能漏掉,否則會報錯的。因為你在xml里 的那些值 根本就沒有實際的值,如果不在這里強制性的setTheme就直接報錯了
19         //在實際使用中 我們當然可以把這個方法 放在baseactivity里面。我們這里默認主題就白天吧
20         setTheme(R.style.dayTheme);
21         super.onCreate(savedInstanceState);
22         setContentView(R.layout.activity_main);
23         //這個就是跟布局
24         view = this.findViewById(R.id.mainview);
25         tv = (TextView) this.findViewById(R.id.tv);
26         tv.setOnClickListener(new View.OnClickListener() {
27 
28             @Override
29             public void onClick(View v) {
30 
31                 //這邊邏輯沒啥好說的 其實可以不需要看啊,就是白天就切換到黑夜 黑夜就切換到白天唄
32                 if (SPUtils.get(MainActivity.this, "theme", "dayTheme").equals("dayTheme")) {
33                     SPUtils.put(MainActivity.this, "theme", "nightTheme");
34                     setTheme(R.style.nightTheme);
35                 } else {
36                     SPUtils.put(MainActivity.this, "theme", "dayTheme");
37                     setTheme(R.style.dayTheme);
38                 }
39                 //這個方法就是實現不重啟頁面切換主題的
40                 ThemeUiUtil.changeTheme(view, getTheme());
41             }
42         });
43     }
44 
45     @Override
46     public boolean onCreateOptionsMenu(Menu menu) {
47         // Inflate the menu; this adds items to the action bar if it is present.
48         getMenuInflater().inflate(R.menu.menu_main, menu);
49         return true;
50     }
51 
52     @Override
53     public boolean onOptionsItemSelected(MenuItem item) {
54         // Handle action bar item clicks here. The action bar will
55         // automatically handle clicks on the Home/Up button, so long
56         // as you specify a parent activity in AndroidManifest.xml.
57         int id = item.getItemId();
58 
59         //noinspection SimplifiableIfStatement
60         if (id == R.id.action_settings) {
61             return true;
62         }
63 
64         return super.onOptionsItemSelected(item);
65     }
66 
67 }

然后看一下切換主題的方法是怎么做的:

 1 package com.example.administrator.mainchangethemeapp;2 3 import android.content.res.Resources;4 import android.util.Log;5 import android.view.View;6 import android.view.ViewGroup;7 import android.widget.AbsListView;8 9 import java.lang.reflect.Field;
10 import java.lang.reflect.InvocationTargetException;
11 import java.lang.reflect.Method;
12 
13 /**
14  * Created by Administrator on 2015/11/14.
15  */
16 public class ThemeUiUtil {
17     /**
18      * 切換應用主題
19      *
20      * @param rootView
21      */
22     public static void changeTheme(View rootView, Resources.Theme theme) {
23         //這裡邏輯很簡單 就是遞歸調用changeTheme-----遞歸調用setTheme了。
24         //注意 你們如果是listview也包含在里面的話 listview自定義實現接口的時候要稍微復雜一些,看你們需要不需要也刷新listview里的item了
25         //這里為了簡單 我就不寫那么復雜了,就這一個邏輯:先set自己的theme 然后遍歷自己的子控件 逐一set
26         if (rootView instanceof ThemeUIInterface) {
27             ((ThemeUIInterface) rootView).setTheme(theme);
28             if (rootView instanceof ViewGroup) {
29                 int count = ((ViewGroup) rootView).getChildCount();
30                 for (int i = 0; i < count; i++) {
31                     changeTheme(((ViewGroup) rootView).getChildAt(i), theme);
32                 }
33             }
34         }
35     }
36 
37 
38 }

?

你看,到這里 我們不重啟activity實現換膚的功能就基本實現了,當然要做的完美的話 還需要各位自己擴充一下 其他控件。但是思路都是一樣的。

?

?

看到這里 有人仍然會說,你這個雖然沒有重啟activity,但是還是不好看呀,還是顯的突兀了,能否做到知乎 android app那樣 切換白天黑夜主題的時候 顯的很柔順呢。

答案是可以的,而且解決方案也比較簡單。就是給個動畫就完事了,在切換前 先保留一下 之前界面的bitmap 然后切換皮膚的的時候 把這個bitmap 顯示出來以后 然后改變他的

alpha值 就可以了,當全部動畫顯示結束以后 把那些不需要的資源全部釋放 就ok了!

來看下代碼,這里就放出點擊事件的代碼了,其他地方與前面的代碼一致:

 1 {2 3                 //我們先取這個根布局的 bitmap緩存 這個實際上跟截屏是差不多的一個東西。4                 view.setDrawingCacheEnabled(true);5                 view.buildDrawingCache(true);6                 final Bitmap localBitmap = Bitmap.createBitmap(view.getDrawingCache());7                 view.setDrawingCacheEnabled(false);8 9                 //這邊邏輯沒啥好說的 其實可以不需要看啊,就是白天就切換到黑夜 黑夜就切換到白天唄
10                 if (SPUtils.get(MainActivity.this, "theme", "dayTheme").equals("dayTheme")) {
11                     SPUtils.put(MainActivity.this, "theme", "nightTheme");
12                     setTheme(R.style.nightTheme);
13                 } else {
14                     SPUtils.put(MainActivity.this, "theme", "dayTheme");
15                     setTheme(R.style.dayTheme);
16                 }
17                 //我們new出來的這個蒙版view --mengbanview 就把他放到跟布局view里面 并且讓他充滿 同時這個view的background就是截屏前我們的那個截圖bitmap
18                 final View mengbanView = new View(getApplicationContext());
19                 mengbanView.setBackgroundDrawable(new BitmapDrawable(getResources(), localBitmap));
20                 ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
21                 ((ViewGroup) view).addView(mengbanView, params);
22                 mengbanView.animate().alpha(0).setDuration(400).setListener(new Animator.AnimatorListener() {
23                     @Override
24                     public void onAnimationStart(Animator animation) {
25                         //這個方法就是實現不重啟頁面切換主題的
26                         ThemeUiUtil.changeTheme(view, getTheme());
27                     }
28 
29                     @Override
30                     public void onAnimationEnd(Animator animation) {
31                         //動畫結束的時候移出這個蒙版view 并釋放bitmap
32                         ((ViewGroup) view).removeView(mengbanView);
33                         localBitmap.recycle();
34                     }
35 
36                     @Override
37                     public void onAnimationCancel(Animator animation) {
38 
39                     }
40 
41                     @Override
42                     public void onAnimationRepeat(Animator animation) {
43 
44                     }
45                 }).start();
46 
47 
48             }

最后來看下效果是否和知乎一樣:

大功告成,看上去自然多了!

轉載于:https://www.cnblogs.com/android-blogs/p/4968941.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/272729.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/272729.shtml
英文地址,請注明出處:http://en.pswp.cn/news/272729.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

訂閱號 圖文回復php,微信開發(PHP實現訂閱號的公眾號配置和自動回復)

首先在微信公眾平臺(網址&#xff1a;https://mp.weixin.qq.com)申請一個訂閱號&#xff0c;然后在開發里找到開發者工具點擊公眾平臺測試賬號&#xff0c;在測試賬號內進行微信開發實驗。 1. 設置一個自己的有效的域名網址和TOKEN(就是暗號)&#xff0c;TOKEN一定要與PHP代…

為何技術領域中女程序員較少?

對于大多數人來說&#xff0c;他們意識中的科技人就是扎克伯格&#xff0c;喬布斯或者是比爾蓋茨——經常出現在新聞里的典型的男科技企業家。其實在科技領域還有一些女性&#xff0c;比如蘇珊沃西基&#xff08;YouTube的CEO&#xff09;和吉尼羅曼提&#xff08;IBM的CEO&…

This version of MySQL doesn't yet support 'LIMIT IN/ALL/ANY/SOME subquery 解決方法

This version of MySQL doesnt yet support LIMIT & IN/ALL/ANY/SOME subquery的意思是&#xff0c;這版本的 MySQL 不支持使用 LIMIT 子句的 IN/ALL/ANY/SOME 子查詢&#xff0c;即是支持非 IN/ALL/ANY/SOME 子查詢的 LIMIT 子查詢。 也就是說&#xff0c;這樣的語句是不能…

php 遞歸分銷角色,PHP遞歸無限分類

我們面試程序開發的時候&#xff0c;經常會被問到遞歸的問題。今天我們在本文中以“素材火的特效分類表”作為演示&#xff0c;使用PHP遞歸結合Mysql實現無限級分類。難易&#xff1a;初級下載資源下載積分&#xff1a;50積分遞歸簡單的說就是一段程序代碼的重復調用&#xff0…

Android音頻系統之AudioPolicyService

地址&#xff1a;http://blog.csdn.net/edmond999/article/details/18599327 1.1 AudioPolicy Service 在AudioFlinger小節&#xff0c;我們反復強調它只是策略的執行者&#xff0c;而AudioPolicyService則是策略的制定者。這種分離方式有效地降低了整個系統的藕合性&#xff0…

程序員常去的14個頂級開發社區

我們來看一下程序員經常去的 14 個頂級開發者社區&#xff0c;如果你還不知道它們&#xff0c;那么趕緊去看看&#xff0c;也許會有意想不到的收獲。一、Stack Overflow9 月份&#xff0c;Stack Overflow 也將迎來其 6 歲的生日&#xff0c;毫無疑問&#xff0c;Stack Overflo…

basename php 中文,php basename不支持中文怎么辦

不久前遇到一段代碼使用了basename&#xff0c;我文件名是中文的&#xff0c;每次返回都只有后綴。后來發現basename函數有貓膩&#xff0c;不支持中文文件名&#xff0c;但凡有以中文開頭的文件名將被忽略了。函數說明/*** 從路徑中返回文件名* param string $path* 在Windows…

C#特性-表達式樹

表達式樹ExpressionTree 表達式樹基礎 轉載需注明出處&#xff1a;http://www.cnblogs.com/tianfan/剛接觸LINQ的人往往覺得表達式樹很不容易理解。通過這篇文章我希望大家看到它其實并不像想象中那么難。您只要有普通的LINQ知識便可以輕松理解本文。表達式樹提供一個將可執行代…

程序員的一個好習慣,你有幾個?

在很多外人看來,作為我們程序員這個行業只要會敲代碼會改bug基本就可以了&#xff0c;但是現在行業人員幾近飽和&#xff0c;沒有過硬的幾把刷子就想著可以甩掉身后的大部隊也是不可能的。所以說我們要不斷的學習不斷的充實自己讓企業在裁員的時候壓根就考慮不到我們的頭上來。…

matlab四維圖程序,Matlab 四維圖形繪制

這個問題可以用slice命令實現&#xff1a;%%%%%%clear all[x,y,z] meshgrid(0:.5:10,0:.5:10,0:.5:10);c x.^2y.^2z.^2;xs [0,2,4,6,8,10];ys [4];zs [6]; % xs,ys,zs可決定切片形式和位置&#xff0c;help slice可明白其具體含義slice(x,y,z,c,xs,ys,zs)colormap hsv當然…

cURL模擬POST方式提交數據

curl_post.php文件&#xff1a; 1 $url http://localhost/test/curl_post_deal.php;2 3 $post_data array( 4 username > cURL, 5 age > 18, 6 sex > male 7 ); 8 $ch curl_init(); 9 10 curl_setopt($ch, CURLOPT_URL, $url); //請求的地址 11 curl_setopt(…

2017已過半,這半年,你累嗎?

大半年下來有人問你掙了多少錢卻從來沒有人問你最近的日子你累不累近來你受了多少委屈近來有多少心酸近來經歷過多少艱難最近付出了多少不被人知的辛苦有時候真的很累、很累但是所有的累只能一笑而過這個累是自己經歷的積累這個累是自己身上應該有的責任除了堅強&#xff0c;別…

mysql 5.5 壓力測試,mysql 壓力測試

mysql 壓力測試sysbench支持的測試模式&#xff1a;1、CPU運算性能2、磁盤IO性能3、調度程序性能4、內存分配及傳輸速度5、POSIX線程性能6、數據庫性能(OLTP基準測試)目前sysbench主要支持 mysql,drizzle,pgsql,oracle 等幾種數據庫。二、編譯安裝sysbench[roottest package]# …

自學編程的六點技巧

有一天&#xff0c;我的一個在學編程的朋友問我&#xff1a;“我想快速學習編程&#xff0c;你有什么好的推薦嗎&#xff1f;我曾在上大學的時候自學過編程&#xff0c;這么多年過去了&#xff0c;我意識到我或許是在用最困難的方式去學習和了解編程。本來我完全可以用更快的速…

AngularJs $anchorScroll、$controller、$document

$anchorScroll 根據HTML5的規則&#xff0c;當調用這個函數時&#xff0c;它檢查當前的url的hash值并且滾動到相應的元素。 監聽$location.hash()并且滾動到url指定的錨點的地方。可以通過$anchorScrollProvider.disableAutoScrolling()禁用。 依賴&#xff1a;$window $loca…

Jquery操作復選框總結

1、獲取單個checkbox選中項(三種寫法)$("input:checkbox:checked").val()或者$("input:[typecheckbox]:checked").val();或者$("input:[nameck]:checked").val();2、 獲取多個checkbox選中項$(input:checkbox).each(function() {if ($(this).att…

java對五排六列考生隨機排座,Java入門第三季6-5擴展練習-隨機字符串排序

原課程地址https://www.imooc.com/video/4254* 擴展練習要求* 利用Collections.sort()方法對泛型為String的List進行排序版本2* 1.創建完List之后&#xff0c;往其中添加10條隨機字符串* 2.每條字符串的長度為10以內的隨機整數* 3.每條字符串的每個字符都是隨機生成的&#xff…

信息安全系統設計基礎第十周學習總結

第八章 異常控制流 &#xff08;1&#xff09;控制流&#xff1a;控制轉移序列。 控制轉移&#xff1a;從一條指令到下一條指令。 異常控制流&#xff1a;現代操作系統通過使控制流發生突變來對系統狀態做出反應&#xff0c;這些突變稱為異常控制流。 &#xff08;2&#xff09…

MVC、 MVP、 MVVM之間的區別

一、 簡介三者的目的都是分離關注&#xff0c;使得UI更容易變換&#xff08;從Winform變為Webform&#xff09;&#xff0c;使得UI更容易進行單元測試。二、MVC/MVP1 、MVC1、View接受用戶的交互請求2、View將請求轉交給Controller3、Controller操作Model進行數據更新4、數據更…

java散列法的運用實例,Java HashMap compute() 使用方法及示例

Java HashMap compute() 使用方法及示例Java HashMap compute()方法計算一個新值&#xff0c;并將其與哈希映射中的指定鍵相關聯。compute()方法的語法為&#xff1a;hashmap.compute(K key, BiFunction remappingFunction)compute()參數compute()方法有兩個參數&#xff1a;ke…