android仿iphone主题效果的主菜单

作者:飞雪连天射白鹿 时间:2023-04-29 03:56:35 

现在很多第三方Launcher((如360Launcher,GoLauncher)带有iphone主题,相信玩Android的人大都知道。

本例实现仿iphone主题的launcher的冰山一角。如下图:

android仿iphone主题效果的主菜单      

从效果看,大概就能猜出用什么控件类(支持左右滑动的控件类+GridView),支持左右滑动的控件类,有很多了比如常用的Gallery,ViewPager,ViewFlipper,ViewFlow等等,本例自定义继承ViewGroup的。看过launcher源码的人应该都知道 有个Workspace类继承ViewGroup实现主菜单的。

闲话不多说了!

主布局:main.xml


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<com.xyz.workspace.Workspace
 android:id="@+id/workspace"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent" />

<com.xyz.workspace.PageIndicator
 android:id="@+id/indicator"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_alignParentBottom="true"
 android:layout_centerHorizontal="true"
 android:layout_marginBottom="20dip" />

</RelativeLayout>

第一个自定义类Workspace就是实现左右滑动的,第二个类PageIndicator做指示器用。
Workspace.java


package com.xyz.workspace;

import java.util.List;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;

public class Workspace extends ViewGroup {

private static final String TAG = "Workspace";
private Scroller mScroller;
private VelocityTracker mVelocityTracker;

private static final int DEFAULT_SCREEN = 0;
private static final int TOUCH_STATE_REST = 0;
private static final int TOUCH_STATE_SCROLLING = 1;
private static final int SNAP_VELOCITY = 600;
public static final int APP_PAGE_SIZE = 16;

private int mCurScreen;
private int mTouchState = TOUCH_STATE_REST;
private int mTouchSlop;
private float mLastMotionX;
private float mLastMotionY;

private OnViewChangedListener mOnViewChangedListener;

public Workspace(Context context, AttributeSet attrs) {
 this(context, attrs, 0);
 // TODO Auto-generated constructor stub
}

public Workspace(Context context, AttributeSet attrs, int defStyle) {
 super(context, attrs, defStyle);
 // TODO Auto-generated constructor stub
 mScroller = new Scroller(context);
 mCurScreen = DEFAULT_SCREEN;
 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
 // TODO Auto-generated method stub
 if (changed) {
  int childLeft = 0;
  final int childCount = getChildCount();
  for (int i = 0; i < childCount; i++) {
   final View childView = getChildAt(i);
   if (childView.getVisibility() != View.GONE) {
    final int childWidth = childView.getMeasuredWidth();
    childView.layout(childLeft, 0, childLeft + childWidth,
      childView.getMeasuredHeight());
    childLeft += childWidth;
   }
  }
 }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);

final int width = MeasureSpec.getSize(widthMeasureSpec);
 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
 if (widthMode != MeasureSpec.EXACTLY) {
  throw new IllegalStateException(
    "ScrollLayout only canmCurScreen run at EXACTLY mode!");
 }

final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
 if (heightMode != MeasureSpec.EXACTLY) {
  throw new IllegalStateException(
    "ScrollLayout only can run at EXACTLY mode!");
 }
 final int count = getChildCount();
 for (int i = 0; i < count; i++) {
  getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
 }
 scrollTo(mCurScreen * width, 0);
}

public void snapToDestination() {
 final int screenWidth = getWidth();
 final int destScreen = (getScrollX() + screenWidth / 2) / screenWidth;
 snapToScreen(destScreen);
}

public void snapToScreen(int whichScreen) {
 whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
 if (getScrollX() != (whichScreen * getWidth())) {
  final int delta = whichScreen * getWidth() - getScrollX();
  mScroller.startScroll(getScrollX(), 0, delta, 0,
    Math.abs(delta) * 2);
  mCurScreen = whichScreen;
  invalidate();
 }
 if (mOnViewChangedListener != null) {
  mOnViewChangedListener.onChange(getChildCount(), whichScreen);
 }
}

public void setToScreen(int whichScreen) {
 whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
 mCurScreen = whichScreen;
 scrollTo(whichScreen * getWidth(), 0);
}

public int getCurScreen() {
 return mCurScreen;
}

@Override
public void computeScroll() {
 // TODO Auto-generated method stub
 if (mScroller.computeScrollOffset()) {
  scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
  postInvalidate();
 }
}

@Override
public boolean onTouchEvent(MotionEvent event) {
 // TODO Auto-generated method stub

if (mVelocityTracker == null) {
  mVelocityTracker = VelocityTracker.obtain();
 }
 mVelocityTracker.addMovement(event);
 final int action = event.getAction();
 final float x = event.getX();
 final float y = event.getY();
 switch (action) {
 case MotionEvent.ACTION_DOWN:
  if (!mScroller.isFinished()) {
   mScroller.abortAnimation();
  }
  mLastMotionX = x;
  break;
 case MotionEvent.ACTION_MOVE:
  int deltaX = (int) (mLastMotionX - x);
  mLastMotionX = x;
  scrollBy(deltaX, 0);
  break;
 case MotionEvent.ACTION_UP:
  final VelocityTracker velocityTracker = mVelocityTracker;
  velocityTracker.computeCurrentVelocity(1000);
  int velocityX = (int) velocityTracker.getXVelocity();
  if (velocityX > SNAP_VELOCITY && mCurScreen > 0) {
   snapToScreen(mCurScreen - 1);
  } else if (velocityX < -SNAP_VELOCITY
    && mCurScreen < getChildCount() - 1) {
   snapToScreen(mCurScreen + 1);
  } else {
   snapToDestination();
  }
  if (mVelocityTracker != null) {
   mVelocityTracker.recycle();
   mVelocityTracker = null;
  }
  mTouchState = TOUCH_STATE_REST;
  break;
 case MotionEvent.ACTION_CANCEL:
  mTouchState = TOUCH_STATE_REST;
  break;
 }
 return true;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
 // TODO Auto-generated method stub
 final int action = ev.getAction();
 if ((action == MotionEvent.ACTION_MOVE)
   && (mTouchState != TOUCH_STATE_REST)) {
  return true;
 }
 final float x = ev.getX();
 final float y = ev.getY();
 switch (action) {
 case MotionEvent.ACTION_MOVE:
  final int xDiff = (int) Math.abs(mLastMotionX - x);
  if (xDiff > mTouchSlop) {
   mTouchState = TOUCH_STATE_SCROLLING;
  }
  break;
 case MotionEvent.ACTION_DOWN:
  mLastMotionX = x;
  mLastMotionY = y;
  mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
    : TOUCH_STATE_SCROLLING;
  break;
 case MotionEvent.ACTION_CANCEL:
 case MotionEvent.ACTION_UP:
  mTouchState = TOUCH_STATE_REST;
  break;
 }
 return mTouchState != TOUCH_STATE_REST;
}

public void setOnViewChangedListener(OnViewChangedListener l) {
 mOnViewChangedListener = l;
}

public interface OnViewChangedListener {
 public void onChange(int cnt, int index);
}
}

PageIndicator.java:


package com.xyz.workspace;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;

public class PageIndicator extends LinearLayout {

private Context mContext;

public PageIndicator(Context ctx) {
 super(ctx);
 // TODO Auto-generated constructor stub
 mContext = ctx;
}

public PageIndicator(Context ctx, AttributeSet attrs) {
 super(ctx, attrs);
 // TODO Auto-generated constructor stub
 mContext = ctx;
}

public void setIndication(int cnt, int index) {
 if (index < 0 || index > cnt)
  index = 0;
 removeAllViews();
 for (int i = 0; i < cnt; i++) {
  ImageView iv = new ImageView(mContext);
  iv.setImageResource(index == i ? R.drawable.indicator_current
    : R.drawable.indicator);
  if (i != 0 || i != cnt - 1) {
   iv.setPadding(4, 0, 4, 0);
  }
  addView(iv);
 }
}
}

这两个类的作用上面已经说了,有什么看不明白的欢迎提问,或自行google。

 ViewGroup实现好了,剩下就是实现GridView显示系统所有app,主要工作也就是实现GridView的适配器---GridAdapter


package com.xyz.workspace;

import java.util.List;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import static com.xyz.workspace.Workspace.APP_PAGE_SIZE;

public class GridAdapter extends BaseAdapter implements OnClickListener {

private Context mContext;
private int mPageIndex;
private List<ResolveInfo> mPackagesInfo;

public GridAdapter(Context context, List<ResolveInfo> listInfo, int page) {
 mContext = context;
 mPackagesInfo = listInfo;
 mPageIndex = page;
}

@Override
public int getCount() {
 // TODO Auto-generated method stub
 int size = mPackagesInfo.size();
 return size / APP_PAGE_SIZE > 0
   && size - (APP_PAGE_SIZE * (mPageIndex + 1)) > 0 ? APP_PAGE_SIZE
   : size % APP_PAGE_SIZE;
}

@Override
public Object getItem(int position) {
 // TODO Auto-generated method stub
 return mPackagesInfo.get(APP_PAGE_SIZE * mPageIndex + position);
}

@Override
public long getItemId(int position) {
 // TODO Auto-generated method stub
 return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
 // TODO Auto-generated method stub
 if (convertView == null) {
  convertView = new AppItem(mContext, (ResolveInfo) getItem(position));
 }
 convertView.setOnClickListener(this);
 convertView.setTag(Integer.valueOf(position));
 return convertView;
}

/** 点击启动app **/
@Override
public void onClick(View v) {
 // TODO Auto-generated method stub
 int pos = (Integer) v.getTag();
 ResolveInfo info = (ResolveInfo) getItem(pos);
 Intent i = new Intent(Intent.ACTION_MAIN);
 i.addCategory(Intent.CATEGORY_LAUNCHER);
 i.setComponent(new ComponentName(info.activityInfo.packageName,
   info.activityInfo.name));
 mContext.startActivity(i);
}
}

GridView的每个item不用说,一看就知道是一个LinearLayout上面是个ImageView,下面一个TextView了。我把它封装了下---AppItem:


package com.xyz.workspace;

import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Bitmap.Config;
import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class AppItem extends RelativeLayout {

private Context mContext;
private ImageView mAppIcon;
private TextView mAppName;
private ResolveInfo mAppInfo;
private PackageManager mPackageManager;

public AppItem(Context context) {
 super(context);
 mContext = context;
 mPackageManager = context.getPackageManager();
 LayoutInflater.from(context).inflate(R.layout.app_item, this);
 mAppIcon = (ImageView) findViewById(R.id.icon);
 mAppName = (TextView) findViewById(R.id.app_name);
}

public AppItem(Context context, ResolveInfo info) {
 this(context);
 mAppInfo = info;
 show();
}

private void show() {
 String packageName = mAppInfo.activityInfo.packageName;
 String appName = mAppInfo.activityInfo.loadLabel(mPackageManager)
   .toString();
 if (appName.equals("拨号")) {
  mAppIcon.setImageResource(R.drawable.com_android_phone);
 } else if (packageName.equals("com.android.contacts")) {
  mAppIcon.setImageResource(R.drawable.com_android_contacts);
 } else if (packageName.equals("com.android.mms")) {
  mAppIcon.setImageResource(R.drawable.com_android_mms);
 } else if (packageName.equals("com.android.music")) {
  mAppIcon.setImageResource(R.drawable.com_android_music);
 } else if (packageName.equals("com.android.browser")) {
  mAppIcon.setImageResource(R.drawable.com_android_browser);
 } else if (packageName.equals("com.android.settings")) {
  mAppIcon.setImageResource(R.drawable.com_android_settings);
 } else if (packageName.equals("com.android.email")) {
  mAppIcon.setImageResource(R.drawable.com_android_email);
 } else if (packageName.equals("com.android.calendar")) {
  mAppIcon.setImageResource(R.drawable.com_android_calendar);
 } else if (packageName.equals("com.android.calculator2")) {
  mAppIcon.setImageResource(R.drawable.com_android_calculator2);
 } else if (packageName.equals("com.android.deskclock")) {
  mAppIcon.setImageResource(R.drawable.com_android_deskclock);
 } else if (packageName.equals("com.android.camera")) {
  mAppIcon.setImageResource(R.drawable.com_android_camera);
 } else if (packageName.equals("com.android.soundrecorder")) {
  mAppIcon.setImageResource(R.drawable.com_android_soundrecorder);
 } else if (packageName.equals("com.tencent.mobileqq")) {
  mAppIcon.setImageResource(R.drawable.com_tencent_qq);
 } else if (packageName.equals("com.tencent.mm")) {
  mAppIcon.setImageResource(R.drawable.com_tencent_mm);
 } else if (packageName.equals("com.tencent.mtt")) {
  mAppIcon.setImageResource(R.drawable.com_tencent_mtt);
 } else if (packageName.equals("com.sina.weibo")) {
  mAppIcon.setImageResource(R.drawable.com_sina_weibo);
 } else if (packageName.equals("com.sds.android.ttpod")) {
  mAppIcon.setImageResource(R.drawable.com_sds_android_ttpod);
  // ////////////////////////////////////////////////////////////////
 } else if (packageName.equals("com.youdao.dict")) {
  mAppIcon.setImageResource(R.drawable.com_youdao_dict);
 } else {
  mAppIcon.setImageDrawable(getRoundCornerDrawable(mContext,
    mAppInfo.activityInfo.loadIcon(mPackageManager), 20));
 }
 mAppName.setText(appName);
}

private Drawable getRoundCornerDrawable(Context ctx, int resId,
  float roundPX /* <span style="font-size:14px;">圆角半径 </span>*/) {
 return getRoundCornerDrawable(ctx,
   mContext.getResources().getDrawable(resId), roundPX);
}

private Drawable getRoundCornerDrawable(Context ctx, Drawable drawable,
  float roundPX /* <span style="font-size:14px;">圆角半径 </span>*/) {
 int w = ctx.getResources()
   .getDimensionPixelSize(R.dimen.app_icon_width);
 int h = w;

Bitmap bitmap = Bitmap
   .createBitmap(
     w,
     h,
     drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
       : Bitmap.Config.RGB_565);
 Canvas canvas = new Canvas(bitmap);
 drawable.setBounds(0, 0, w, h);
 drawable.draw(canvas);

int width = bitmap.getWidth();
 int height = bitmap.getHeight();
 Bitmap retBmp = Bitmap.createBitmap(width, height, Config.ARGB_8888);
 Canvas can = new Canvas(retBmp);

final int color = 0xff424242;
 final Paint paint = new Paint();
 final Rect rect = new Rect(0, 0, width, height);
 final RectF rectF = new RectF(rect);

paint.setColor(color);
 paint.setAntiAlias(true);
 can.drawARGB(0, 0, 0, 0);
 can.drawRoundRect(rectF, roundPX, roundPX, paint);

paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
 can.drawBitmap(bitmap, rect, rect, paint);
 return new BitmapDrawable(retBmp);
}
}

注意咯,show函数就是替换显示对应iphone里app的图标(来源反编译iphone主题的launcher或锁屏),利用 包名 判断是哪个应用再换上对应图标,例如com.android.mms---信息,com.android.contacts---联系人,这里有个疑问,为什么phone模块的package_name的也是com.android.contacts,有人知道么?谢谢啦!
AppItem引用一个布局:
app_item.xml:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/app_icon_width"
android:layout_height="@dimen/app_icon_height"
android:gravity="center"
android:orientation="vertical" >

<ImageView
 android:id="@+id/icon"
 android:layout_width="@dimen/app_icon_width"
 android:layout_height="@dimen/app_icon_width"
 android:layout_gravity="center_horizontal" />

<TextView
 android:id="@+id/app_name"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_gravity="center_horizontal"
 android:ellipsize="marquee"
 android:maxWidth="@dimen/app_icon_height"
 android:singleLine="true"
 android:textColor="@android:color/white"
 android:textSize="12sp" />

</LinearLayout>

主Activity就是获取所有app信息及初始化界面,
MainActivty.java:


package com.xyz.workspace;

import java.util.List;

import com.xyz.workspace.Workspace.OnViewChangedListener;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.view.Gravity;
import android.widget.GridView;
import static com.xyz.workspace.Workspace.APP_PAGE_SIZE;

public class MainActivity extends Activity implements OnViewChangedListener {

private Workspace mWorkspace;
private PageIndicator mIndicator;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.main);

mWorkspace = (Workspace) findViewById(R.id.workspace);
 mIndicator = (PageIndicator) findViewById(R.id.indicator);
 List<ResolveInfo> apps = loadApps();
 for (int i = 0; i < Math.ceil(1.0f * apps.size() / APP_PAGE_SIZE); i++) {
  GridView grid = new GridView(this);
  grid.setNumColumns(4);
  grid.setHorizontalSpacing(10);
  grid.setVerticalSpacing(40);
  grid.setPadding(30, 50, 30, 20);
  grid.setGravity(Gravity.CENTER);
  grid.setAdapter(new GridAdapter(this, apps, i));
  mWorkspace.addView(grid);
 }
 mWorkspace.setOnViewChangedListener(this);
 mIndicator.setIndication(mWorkspace.getChildCount(), 0);
}

private List<ResolveInfo> loadApps() {
 Intent i = new Intent(Intent.ACTION_MAIN, null);
 i.addCategory(Intent.CATEGORY_LAUNCHER);
 return getPackageManager().queryIntentActivities(i, 0);
}

@Override
public void onChange(int cnt, int index) {
 // TODO Auto-generated method stub
 mIndicator.setIndication(cnt, index);
}
}

源码下载:android仿iphone主题之主菜单

来源:http://blog.csdn.net/zhouyuanjing/article/details/8561503

标签:android,iphone,主菜单
0
投稿

猜你喜欢

  • Android在项目中接入腾讯TBS浏览器WebView的教程与注意的地方

    2021-12-18 01:58:28
  • Android RecyclerView 实现快速滚动的示例代码

    2023-02-17 05:32:50
  • 举例讲解Java的Hibernate框架中的多对一和一对多映射

    2023-09-05 23:05:53
  • 分享WCF聊天程序--WCFChat实现代码

    2023-01-19 17:04:59
  • Android使用DrawerLayout实现仿QQ双向侧滑菜单

    2023-07-18 23:48:24
  • Java和Ceylon对象的构造和验证

    2022-04-05 04:28:37
  • SpringBoot项目打jar包与war包的详细步骤

    2022-01-12 01:28:21
  • Java程序命令行参数用法总结

    2022-09-22 11:09:32
  • Java基于正则实现的日期校验功能示例

    2021-09-30 02:43:26
  • spring mvc url匹配禁用后缀访问操作

    2021-11-19 23:57:34
  • Java注解详细介绍

    2021-10-09 19:52:28
  • Java中static静态变量的初始化完全解析

    2023-11-27 21:03:39
  • 使用java生成字母验证码

    2021-10-29 23:50:25
  • Android ListView 条目多样式展示实例详解

    2022-10-22 06:18:41
  • Android使用Scroll+Fragment仿京东分类效果

    2022-11-17 16:56:48
  • SpringMvc+Angularjs 实现多文件批量上传

    2023-12-08 23:25:24
  • Java获取json数组对象的实例讲解

    2023-08-24 14:55:28
  • android通过usb读取U盘的方法

    2023-03-14 07:43:25
  • VMware虚拟机下hadoop1.x的安装方法

    2023-07-27 08:01:40
  • C++ vector数组用法及解析

    2022-09-20 01:04:25
  • asp之家 软件编程 m.aspxhome.com