android仿iphone主题效果的主菜单

2020-10-28 12:42:13 浏览数 (1)

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

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

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

闲话不多说了!

主布局:main.xml

代码语言:javascript复制
<?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

代码语言:javascript复制
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:

代码语言:javascript复制
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

代码语言:javascript复制
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:

代码语言:javascript复制
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:

代码语言:javascript复制
<?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:

代码语言:javascript复制
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); 
} 
}

以上就是本文的全部内容,希望对大家的学习有所帮助。

0 人点赞