网上也有一些仿微信摇一摇的,但大部分都不完整。今天就自己动手来仿一下。有图的当然先上一下图:
触发摇一摇后手机还有音效和震动效果,gif展示不出来。另外在设置界面还能更改背景图,设置音效的开关,点击更换后打开一个图片选择器,这里不会讲这个选择器,有兴趣的可以参照鸿神的博客,我的大部分也是参照那上面来的,链接:http://blog.csdn.net/lmj623565791/article/details/39943731。
1. 摇一摇界面
其中手机震动使用的是Vibrator,音效使用的是SoundPool,使用都不难,看文档即可。背景图的URL、是否使用音效保存在sharedPreference内,onResume中获取并进行更新。图片的加载开启了一个子线程,避免阻碍UI线程,加载完毕后再handler中对隐藏图片进行更新。
3. 摇一摇设置
摇一摇的设置界面主要提供三个功能:使用默认背景图片,更换背景图片以及开启或关闭音效,值都是保存再sharedPreference中。点击更换背景图时打开图片选择器,选择图片后返回图片的URL并进行保存。主要的代码如下:
这样就大体上完成了微信的摇一摇功能了~
1. 摇一摇界面
首先看一下界面,上面的标题栏没什么好说的,我用的是toolbar。中间应该有两个ImageView,触发摇一摇后应该有个动画,两个imageview分别向上下运动,显示出隐藏的图片,结束后再返回。所以中间应该还有个隐藏的imageview,而且以那两个imageview为边界并且完全被遮挡住。下面可以用三个linearlayout,每个都包含图片和文字,点击后图片变色并且改变标题内容,布局文件如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#2b2b2b">
<include layout="@layout/tool_bar" />
<ImageView
android:id="@+id/iv_shake_imageHidden"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:scaleType="centerCrop"
android:src="@drawable/a5z" />
<ImageView
android:id="@+id/iv_shake_imageUp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/bg_shake_image_up" />
<ImageView
android:id="@+id/iv_shake_imageDown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/bg_shake_image_down" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:padding="16dp">
<LinearLayout
android:id="@+id/ll_shake_people"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_shake_people"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:src="@drawable/bg_shake_people" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人"
android:textColor="#ffffff"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_shake_music"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_shake_music"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:src="@drawable/bg_shake_music" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="歌曲"
android:textColor="#ffffff"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_shake_tv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_shake_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:src="@drawable/bg_shake_tv" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="电视"
android:textColor="#ffffff"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
其中的图片资源我都提前准备好了。注意一下中间的三个imageview的位置是还没确定的,需要在代码中测得控件高度后进行偏移后才能完成,而在onCreate方法中直接测量控件高度是为0的,因为此时控件还没进行测量绘制。可以用过一个post方法执行,这样能保证控件已测量完成:
private void initView(){
// …初始化控件
imageHidden.post(new Runnable() {
@Override
public void run() {
int imageHeight = imageDown.getHeight();
imageUp.setTranslationY(-imageHeight/2);
imageDown.setTranslationY(imageHeight/2);
ViewGroup.LayoutParams params = imageHidden.getLayoutParams();
params.height = imageHeight*2;
imageHidden.setLayoutParams(params);
}
});
这样才能让上下两张图片刚好位于中间,而隐藏的图片也刚好被遮住。
2.摇一摇效果
摇一摇是通过手机的运动来判定的,需要使用加速度传感器。控制传感器的类是Sensor,在onCreate方法中初始化,分别在onResume和onPause中注册和注销,注册时需要传入监听器,当传感器测得数值发生改变时会回调onSensorChanged方法,在该方法内获得加速度并进行判断,如果大于阈值则触发摇一摇。摇一摇的效果包括三个,两个imageview的动画,手机震动以及手机音效。相关的代码如下:
public class ShakeActivity extends AppCompatActivity implements View.OnClickListener,SensorEventListener{
private static final int minValue = 15;
private static final long[] vibratePattern = {100,200,100,200};
// -1表示只震动一次,非-1表示从pattern的指定下标开始重复振动
private static final int vibrateRepeat = -1;
// …控件的声明
private SensorManager sensorManager;
private Sensor vibrateSensor;
// 实现震动的类
private Vibrator vibrator;
// 上面的动画
private Animation bounceUpAnimation;
// 下面的动画
private Animation bounceDownAnimation;
// 是否允许摇动,用来防止短时间内连续触发事件
private boolean allowShake = true;
// 产生音效的类
private SoundPool mSoundPool;
// 音效的ID
private int soundId;
// 是否开启音效
private boolean useSound;
// 背景图
private String currentImage;
// 用于在子线程加载完毕后更新背景图
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
Bitmap bitmap = (Bitmap) msg.obj;
imageHidden.setImageBitmap(bitmap);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_shake);
initView();
setupAnimation();
// 默认的背景图
currentImage = ShakeSettingActivity.DEFAULT_IMAGE;
}
private void setupAnimation() {
bounceUpAnimation = AnimationUtils.loadAnimation(this,R.anim.translate_up_down);
bounceDownAnimation = AnimationUtils.loadAnimation(this,R.anim.translate_down_up);
bounceUpAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
allowShake = false;
vibrator.vibrate(vibratePattern, vibrateRepeat);
if(useSound){
mSoundPool.play(soundId,1,1,0,0,1);
}
}
@Override
public void onAnimationEnd(Animation animation) {
allowShake = true;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
@Override
protected void onResume() {
super.onResume();
sensorManager.registerListener(this, vibrateSensor,SensorManager.SENSOR_DELAY_NORMAL);
soundId = mSoundPool.load(this,R.raw.shake_sound_male,0);
SharedPreferences preferences = getSharedPreferences(ShakeSettingActivity.PREF_NAME,MODE_PRIVATE);
useSound = preferences.getBoolean(ShakeSettingActivity.PREF_KEY_USE_SOUND,true);
final String imageUrl = preferences.getString(ShakeSettingActivity.PREF_KEY_BACKGROUND,ShakeSettingActivity.DEFAULT_IMAGE);
if(imageUrl != currentImage){
// 如果图片不同则进行更新
currentImage = imageUrl;
if(imageUrl.equals(ShakeSettingActivity.DEFAULT_IMAGE)){
// 重置为默认图片
imageHidden.setImageResource(R.drawable.a5z);
return;
}
new Thread(new Runnable() {
@Override
public void run() {
// 开启一个线程加载图片
Bitmap bitmap = BitmapFactory.decodeFile(imageUrl);
Message message = Message.obtain();
message.obj = bitmap;
mHandler.sendMessage(message);
}
}).start();
}
}
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(this);
mSoundPool.release();
}
private void initView() {
// …初始化控件
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
vibrateSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
mSoundPool = new SoundPool(5, AudioManager.STREAM_MUSIC,0);
imageHidden.post(new Runnable() {
@Override
public void run() {
int imageHeight = imageDown.getHeight();
imageUp.setTranslationY(-imageHeight/2);
imageDown.setTranslationY(imageHeight/2);
ViewGroup.LayoutParams params = imageHidden.getLayoutParams();
params.height = imageHeight*2;
imageHidden.setLayoutParams(params);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_shake,menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.menu_item_setting:
startActivity(new Intent(this,ShakeSettingActivity.class));
}
return super.onOptionsItemSelected(item);
}
@Override
public void onClick(View v) {
clearState();
switch (v.getId()){
case R.id.ll_shake_people:
toolbar.setTitle("摇一摇");
viewPeople.setSelected(true);
break;
case R.id.ll_shake_music:
toolbar.setTitle("摇歌曲");
viewMusic.setSelected(true);
break;
case R.id.ll_shake_tv:
toolbar.setTitle("摇电视");
viewTv.setSelected(true);
break;
}
}
private void clearState() {
viewPeople.setSelected(false);
viewMusic.setSelected(false);
viewTv.setSelected(false);
}
@Override
public void onSensorChanged(SensorEvent event) {
if(!allowShake){
return;
}
float[] values = event.values;
// z轴减去重力加速度9.8
if(Math.abs(values[0])>minValue||Math.abs(values[1])>minValue||Math.abs(values[2]-9.8)>minValue){
imageUp.startAnimation(bounceUpAnimation);
imageDown.startAnimation(AnimationUtils.loadAnimation(this,R.anim.translate_down_up));
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}
其中手机震动使用的是Vibrator,音效使用的是SoundPool,使用都不难,看文档即可。背景图的URL、是否使用音效保存在sharedPreference内,onResume中获取并进行更新。图片的加载开启了一个子线程,避免阻碍UI线程,加载完毕后再handler中对隐藏图片进行更新。
3. 摇一摇设置
摇一摇的设置界面主要提供三个功能:使用默认背景图片,更换背景图片以及开启或关闭音效,值都是保存再sharedPreference中。点击更换背景图时打开图片选择器,选择图片后返回图片的URL并进行保存。主要的代码如下:
public class ShakeSettingActivity extends AppCompatActivity implements View.OnClickListener{
// sharedPreference文件中保存是否使用音效的KEY
public static final String PREF_KEY_USE_SOUND = "shake_use_sound";
// sharedPreference文件中保存图片url的KEY
public static final String PREF_KEY_BACKGROUND = "shake_background";
// sharedPreference文件名
public static final String PREF_NAME = "Setting";
// 用于表示默认图片
public static final String DEFAULT_IMAGE = "default_image";
private static final int CODE_SELECT_IMAGE = 0;
private TextView tvSetDefaultImage;
private TextView tvChangeImage;
private TextView tvPeople;
private TextView tvMessage;
private TextView tvHistory;
private ToggleButton tgBtnSound;
private boolean useSound;
private SharedPreferences mSharedPreferences;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_shake_setting);
initView();
initData();
}
@Override
protected void onPause() {
super.onPause();
if(tgBtnSound.isSelected() != useSound){
// 不相同说明设置发生了变化,需要写回sharedPreference文件
mSharedPreferences.edit().putBoolean(PREF_KEY_USE_SOUND,useSound).commit();
}
}
private void initData() {
mSharedPreferences = getSharedPreferences(PREF_NAME,MODE_PRIVATE);
// 获取mSharedPreferences中保存的值
useSound = mSharedPreferences.getBoolean(PREF_KEY_USE_SOUND,true);
tgBtnSound.setSelected(useSound);
}
private void initView() {
Toolbar toolbar = (Toolbar) findViewById(R.id.tool_bar);
toolbar.setTitle("摇一摇设置");
toolbar.setNavigationIcon(R.drawable.actionbar_icon_back);
setSupportActionBar(toolbar);
tvSetDefaultImage = (TextView) findViewById(R.id.tv_shakeSetting_setDefaultImage);
tvChangeImage = (TextView) findViewById(R.id.tv_shakeSetting_changeImage);
tvPeople = (TextView) findViewById(R.id.tv_shakeSetting_people);
tvMessage = (TextView) findViewById(R.id.tv_shakeSetting_message);
tvHistory = (TextView) findViewById(R.id.tv_shakeSetting_history);
tgBtnSound = (ToggleButton) findViewById(R.id.tgBtn_sound);
tvSetDefaultImage.setOnClickListener(this);
tvChangeImage.setOnClickListener(this);
tvPeople.setOnClickListener(this);
tvMessage.setOnClickListener(this);
tvHistory.setOnClickListener(this);
tgBtnSound.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.tv_shakeSetting_setDefaultImage:
saveBackground(DEFAULT_IMAGE);
Toast.makeText(this,"已恢复默认背景",Toast.LENGTH_SHORT).show();
break;
case R.id.tv_shakeSetting_changeImage:
Intent intent = new Intent(this,ChooseImageActivity.class);
intent.putExtra(ChooseImageActivity.KEY_SELECT_MODE,ChooseImageActivity.MODE_SINGLE);
startActivityForResult(intent, CODE_SELECT_IMAGE);
break;
case R.id.tgBtn_sound:
tgBtnSound.setSelected(!tgBtnSound.isSelected());
break;
default:
Toast.makeText(this,"有待开发",Toast.LENGTH_SHORT).show();
}
}
private void saveBackground(String image) {
mSharedPreferences.edit()
.putString(PREF_KEY_BACKGROUND,image)
.commit();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode){
case CODE_SELECT_IMAGE:
if(resultCode == RESULT_OK){
// 结果正常,取得照片的URL并保存
String[] image = data.getStringArrayExtra(ChooseImageActivity.KEY_CONTENT);
saveBackground(image[0]);
}
break;
}
}
}
这样就大体上完成了微信的摇一摇功能了~