0%

LevelListDrawable是Drawable的一个子类,顾名思义,这是一组图片,根据不同的Level,确定显示哪一张.比如说显示信号的图标,其实每个信号等级都是一张不同的图片,如果不用LevelListDrawable,那么需要根据信号强度判断切换显示指定的图标,而使用LevelListDrawable后,只需要setLevel就可以了. 第一步,准备好各个level的图片,再把他们定义到一个xml文件中: btn_playmethod.xml

<?xml version="1.0" encoding="utf-8"?>
 <level-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:maxLevel="0" android:drawable="@drawable/ic_playmethod_normal" />
    <item android:maxLevel="1" android:drawable="@drawable/ic_playmethod_repeat_list" />
    <item android:maxLevel="2" android:drawable="@drawable/ic_playmethod_repeat_one" />
    <item android:maxLevel="3" android:drawable="@drawable/ic_playmethod_random" />
</level-list>

然后在ImageView中跟普通资源一样调用.

            <ImageView
                android:id="@+id/iv_playmethod"
                android:layout_width="50dp"
                android:layout_height="40dp"
                android:layout_marginTop="5dp"
                android:scaleType="center"
                android:src="@drawable/btn_playmethod" />

下面是点击ImageView后切换图片:

        LevelListDrawable listDrawable=(LevelListDrawable)iv_playmethod.getDrawable();
        int currentMethod=listDrawable.getLevel();
        int nextMethod=Constants.PLAY_METHOD_NORMAL;
        if(currentMethod

强制转换成LevelListDrawable,就可以调用getLevel,setLevel方法,LevelListDrawable会自动找到对应的图片显示出来.

向后引用用于重复搜索前面某个分组匹配的文本.比如查询中文AABB式的成语:

        Pattern p=Pattern.compile("([\u4e00-\u9fa5])\\1([\u4e00-\u9fa5])\\2");
        Matcher matcher=p.matcher("sfdasfd轰轰烈烈asdfsaf高高兴兴sfasfd");
        while(matcher.find())
        {
            System.out.println(matcher.group(0));
        }

\\1表示重复第一个括号里的内容,\\2表示重复第二个括号里的内容 输出”轰轰烈烈”和”高高兴兴”.

有个项目用了mongodb数据库,查询条件有and也有or,按Thinkphp官方手册,使用复合查询(_complex),getLastSql输出查询语句,发现查询条件是空的.用字符串模式查询(_string),请求字符串查询(_query)无法满足需求.估计用mongodb的用户不多,thinkphp官方对这方面支持也不够.打开thinkphp的mongodb驱动,Thinkphp/Extend/Driver/Db/DbMongo.class.php,找到protected function parseThinkWhere($key,$val)方法,可以发现,switch里没有_complex,也就是说,Thinkphp使用mongodb时,根本不支持复合查询.加上:

            case '_complex'://复合查询
                $arr   = array();
                foreach ($val as $nkey=>$nval){
                    if( strpos($nkey,'_')!=0)
                    {
                        $parseArr=$this->parseWhereItem($nkey,$nval);
                        //转换成对象
                        $obj=new stdClass();
                        foreach ($parseArr as $pkey=>$pval)
                        {
                            $obj->$pkey=$pval;
                        }
                        array_push($arr, $obj);
                    }
                }
                if(isset($val['_logic']) && strtolower($val['_logic']) == 'or' ) {
                    unset($val['_logic']);
                    $query['$or']   =  $arr;
                }
                break;

这里之所以要转换成对象,是因为使用thinkphp使用json_encode函数生成查询语句,但是如果数组元素带key,json_encode函数会把数组转换成对象的形式,mongodb不能识别.因为目前只用到or,所以,代码只对or作了处理. 另外,发现个BUG(不知道算不算),在parseWhere方法中:

foreach ($where as $key=>$val){
            if('_id' != $key && 0===strpos($key,'_')) {
                // 解析特殊条件表达式
                //原 $query=$this->parseThinkWhere($key,$val);
                $query   = array_merge($query,$this->parseThinkWhere($key,$val));
            }else{
                // 查询字段的安全过滤
                if(!preg_match('/^[A-Z_\|\&\-.a-z0-9]+$/',trim($key))){
                    throw_exception(L('_ERROR_QUERY_').':'.$key);
                }
                $key = trim($key);
                if(strpos($key,'|')) {
                    $array   =  explode('|',$key);
                    $str   = array();
                    foreach ($array as $k){
                        $str[]   = $this->parseWhereItem($k,$val);
                    }
                    $query['$or'] =    $str;
                }elseif(strpos($key,'&')){
                    $array   =  explode('&',$key);
                    $str   = array();
                    foreach ($array as $k){
                        $str[]   = $this->parseWhereItem($k,$val);
                    }
                    $query   = array_merge($query,$str);
                }else{
                    $str   = $this->parseWhereItem($key,$val);
                    $query   = array_merge($query,$str);
                }
            }
        }

解析特殊条件表达式时,源代码里是$query=$this->parseThinkWhere($key,$val);当特殊表达式在where数组里不是第一个元素时,就出错了,else里的代码得到的$query数组,都没了.

默认情况下,背景属性是平铺的,如果背景图片较大,而box小,那么就只能显示左上角那块背景.如果要让背景全部显示出来,

.color_checked {
    float: right;
    width: 16px;
    height: 16px;
    background: url(../images/color_check.png) center 0 no-repeat;
    -webkit-background-size: cover;
    -moz-background-size: cover;
    -o-background-size: cover;
    background-size: cover;
}

因为li要横排,所以必须加上float:left属性,而ul是没有设高度,自适应的.但是现在发现,把li浮动到左侧以后,外层的ul高度没有包裹住li,高度是0.其实,这是因为float以后,没有清除浮动的原因. 所以解决方法是在所有的li后,clear float:

        <ul>
            <li class="radioButton"></li>
            <li class="radioButton"></li>
            <li class="radioButton"></li>
            <div class="clear"></div>
        </ul>

预期的效果是实现了,但是在ul里加个div,Zend studio提示”标记(div)的位置无效。”,不是很完美的方法.

这里用apache的commons-email(http://commons.apache.org/proper/commons-email/download_email.cgi)和java mail(http://www.oracle.com/technetwork/java/index-138643.html)发送邮件. 我的需求仅仅是在系统异常时发个报警邮件,所以,不涉及到附件之类的东西,代码很简单:

public static void sendMail(String receiverAddress,String subject,String message)
    {
    MultiPartEmail email = new MultiPartEmail();  
        try {  
            //smtp server:  
            email.setHostName("smtp.163.com");  
            // Charset  
            email.setCharset("utf-8");  
            // receiver's address            
            email.addTo(receiverAddress);  
            // 发件人邮箱
            email.setFrom("***@163.com");  
            //帐号密码
            email.setAuthentication("***", "***"); 
            //主题
            email.setSubject(subject);  
            //信息 
            email.setMsg(message);  
            // 发送  
            email.send();  
        } catch (EmailException e) {  
            e.printStackTrace();  
        }  
    }

Service相信大家都很熟悉,一个没有界面的服务,但其实它不是后台的,所有的代码默认在UI线程执行,若要执行耗时操作,需要新开线程或者使用AsyncTask. IntentService继承自Service,所以,它也是一个服务,但与Service有些区别: 1,Service默认在UI线程执行,而IntentService的onHandleIntent方法在后台执行 2,Service在start后,如果没有手动stop,会一直存在,而IntentService,在执行完后,自动退出. 如果IntentService未执行完上次任务,再次被start,不会再开一个新线程来执行新任务,而是等待之前的任务执行完,再执行新的任务,待所有任务执行完,再stopSelf. 通过查看IntentService的源码,可以发现,其实原理很简单.在OnCreate里创建一个新线程,得到该线程的Looper对象,通过该Looper对象构建一个Handler,在onStart里把Intent当作参数传给该Handler,以实现在OnCreate里创建的线程里运行的目标.在处理完所有任务后, stopSelf(msg.arg1),该msg.arg1就是startId,即每次startService时自动生成的一个唯一id,stopSelf(startId)方法,会判断传入的startId,是不是最后一次调用startService时生成的startId,如果不是,不会关闭服务,是则关闭.即完成最后一次startService的任务后,才关闭Service. 下面是一个小例子: MainActivity.java;

package com.example.androidtest;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {

    Button button1,button2;
    Handler threadAHanlder;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button1=(Button)findViewById(R.id.button1);
        button2=(Button)findViewById(R.id.button2);
        
        button1.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO 自动生成的方法存根
                Intent intent=new Intent(MainActivity.this,MyService.class);
                startService(intent);
            }
        });
        
        button2.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO 自动生成的方法存根
                Intent intent=new Intent(MainActivity.this,MyIntentService.class);
                startService(intent);
            }
        });
    }

}

MyService.java:

package com.example.androidtest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class MyService extends Service {

    @Override
    public void onCreate() {
        // TODO 自动生成的方法存根
        super.onCreate();
        System.out.println("Service onCreate");
    }
    @Override
    public IBinder onBind(Intent intent) {
        // TODO 自动生成的方法存根
        return null;
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //在UI线程执行,会阻塞UI,导致ANR
        for(int i=0;i<10;i++)
        {
            System.out.println("Service "+Thread.currentThread().getId()+" "+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            }
        }		
        return super.onStartCommand(intent, flags, startId);
    }
    
    @Override
    public void onDestroy() {
        //没有手动调stopService,不会自己关闭
        super.onDestroy();
        System.out.println("Service Destory");
    }

}

MyIntentService.java:

package com.example.androidtest;

import android.app.IntentService;
import android.content.Intent;

public class MyIntentService extends IntentService {

    //没有这个空的构造函数,启动会报错
    public MyIntentService()
    {
        super("aaaa");
    }
    @Override
    public void onCreate() {
        // TODO 自动生成的方法存根
        super.onCreate();
        System.out.println("IntentService onCreate");
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        //若没有执行完,再次start,等待这次执行完后才执行下一次调用,线程id不会变
        for(int i=0;i<10;i++)
        {
            System.out.println("IntentService "+Thread.currentThread().getId()+" "+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    @Override
    public void onDestroy() {
        //所有任务执行完后,自动关闭
        super.onDestroy();
        System.out.println("IntentService Destory");
    }


}

ListView,Gallery,GridView等控件,在载入大量图片时,很容易会产生OutOfMemoryError异常,即内存溢出.因为每个应用可用内存是有限的,但是图片却很占内存,JPG,PNG本身就是压缩格式,如果分辨率高,很可能保存到磁盘中只有几百K,但是读到内存中会占用十几M内存.图片一多,自然就OutOfMemoryError了. 解决这个问题,需要考虑两个方面. 一是按需载入图片,即读图片的缩略图.比如,每个ImageView在屏幕上的面积是120dp*120dp,但是这个图片是高清的,分辨率2560*1600,如果直接载入整个图片,占用的内存是2560*1600*4/1024/1024=15.625M,如果一屏显示5个Item,占用的内存就是78.125M!而Galaxy Nexus给每个应用分配的内存只有64M,低配机型更少.所以,这里必须根据需要的分辨率载入图片.BitmapFactory解析图片时可以传入一个BitmapFactory.Options参数.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

这里最重要的是inSampleSize,这是一个int值,等于1时,返回原图,大于1时,返回宽高为原图1/inSampleSize的图片, 所以,我们需要先根据需要的大小,计算inSampleSize.

        BitmapFactory.Options options = new BitmapFactory.Options();
                //设为true,不会去解析图片,只解析边界,即宽高
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(jpgPath, options);
         //读出图片真实的宽高
        int[] size = new int[2];
        size[0] = options.outWidth;
        size[1] = options.outHeight;

        float realWidth = size[0];
        float realHeight = size[1];

        // 如果图片尺寸比最大值小,直接返回
        if (maxWidth > realWidth && maxHeight > realHeight) {
            return 1;
        }
        // 计算宽高比
        float target_ratio = (float) maxWidth / maxHeight;
        float real_ratio = realWidth / realHeight;
        int inSampleSize = 1;
        if (real_ratio > target_ratio) {
            // 如果width太大,height太小,以width为基准,把realWidth设为maxWidth,realHeight缩放
            inSampleSize = (int) realWidth / maxWidth;
        } else {
            inSampleSize = (int) realHeight / maxHeight;
        }

虽然经过上面的处理,图片占用的内存已经大幅减少,放个百来个Item也不会OutOfMemoryError了,但很多应用都是下拉刷新,增加Item,Item的数量不可控,如果所有的图片都放内存里,溢出也是早晚的事.这里就需要做个图片缓存,内存里只保留需要用到的,没用到的,释放.我这里做了二级缓存,第一级放内存,第二级放SD卡. 说到图片内存缓存,网上大部分文章推荐软引用(SoftReference) 和弱引用(WeakReference),但是从Android 2.3开始,所有软引用和弱引用的对象在GC时都会被回收,根本起不到缓存的作用,Google推荐使用LruCache(http://developer.android.com/reference/android/util/LruCache.html),该类可以分配指定大小的缓存空间,当达到临界值时,最先保存的对象会被挤出,释放内存. 下面是一个使用LruCache的适配器:

package com.pocketdigi.googleimage;

import java.util.List;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.pocketdigi.googleimage.mode.ApiResult.ImageData;
import com.pocketdigi.utils.ImageUtils;
import com.pocketdigi.utils.ImageUtils.DownBitmapListener;
import com.pocketdigi.views.ImageViewer;

public class GoogleImageAdapter extends BaseAdapter{
    Context mContext;
    List mImageList;
    LayoutInflater mInflater;
    boolean mBusy = false;
    // 图片缓存,Key是url,Value就是Bitmap
    LruCache mImageCache;
    public GoogleImageAdapter(Context context, List imageList) {
        mContext = context;
        mImageList = imageList;
        mInflater = LayoutInflater.from(mContext);
        //读取应用可用内存大小,这里读出来,单位是兆,Galaxy Nexus是64M
        final int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
        //取1/8作为图片缓存
        final int maxSize = 1024 * 1024 * memClass / 8;
        mImageCache = new LruCache(maxSize) {
            // 必须覆写sizeOf方法,因为默认返回的是1,即统计的是对象的数量,而不是占用的内存
            @Override
            protected int sizeOf(String key, Bitmap value) {
                // TODO 自动生成的方法存根
                return value.getByteCount();
            }
        };

    }

    @Override
    public int getCount() {
        // TODO 自动生成的方法存根
        return mImageList.size();
    }

    @Override
    public Object getItem(int position) {
        // TODO 自动生成的方法存根
        return mImageList.get(position);
    }

    @Override
    public long getItemId(int position) {
        // TODO 自动生成的方法存根
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // TODO 自动生成的方法存根
        final ImageData imageData = mImageList.get(position);
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.item_listview, null);
            holder = new ViewHolder();
            holder.tv_title = (TextView) convertView.findViewById(R.id.tv_title);
            holder.iv_thumbnail = (ImageView) convertView.findViewById(R.id.iv_thumbnail);
            holder.thumbnail_url = imageData.getThumbnail_url();
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
            if (!holder.thumbnail_url.equals(imageData.getThumbnail_url())) {
                holder.iv_thumbnail.setImageResource(R.drawable.loading);
            }
        }

        holder.tv_title.setText(imageData.getAbs());
        if (!isBusy()) {
            final String imgUrl = imageData.getThumbnail_url();
            Bitmap bmp = mImageCache.get(imgUrl);
            if (bmp != null) {
                holder.iv_thumbnail.setImageBitmap(bmp);
            } else {
                loadBmpFromNetWork(imgUrl);
            }

        }

        holder.iv_thumbnail.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                //点击ImageView显示大图
                ImageViewer imageViewer = new ImageViewer(mContext);
                imageViewer.setSourceView((ImageView) v);
                imageViewer.setJpgUrl(imageData.getImage_url());
                imageViewer.show();
            }
        });

        return convertView;
    }
    /**
     * 从网络下载图片
     * @param imgUrl
     */
    private void loadBmpFromNetWork(final String imgUrl) {
        //异步从网络下载图片,这个异步下载方法,是有作磁盘缓存的
        //缓存原理,根据传入的url,计算md5,作缓存文件名,下载前先判断该文件存不存在,如果存在,从SD卡读,不存在,再去下载
        //参数120,120,是返回120*120的bitmap,避免在载入大图时浪费内存
        ImageUtils.asynchronousDownloadBitmap(imgUrl, 120, 120, new DownBitmapListener() {
            @Override
            public void onComplete(Bitmap bmp) {
                // TODO 自动生成的方法存根
                if(imgUrl!=null&&bmp!=null)
                {
                    mImageCache.put(imgUrl, bmp);
                    notifyDataSetChanged();
                }
            }

            @Override
            public void onProgressChanged(long total, long downloaded) {
                // TODO 自动生成的方法存根

            }
        });
    }

    @Override
    public int getItemViewType(int position) {
        // TODO 自动生成的方法存根
        return super.getItemViewType(position);
    }

    @Override
    public int getViewTypeCount() {
        // TODO 自动生成的方法存根
        return super.getViewTypeCount();
    }
    //用来保存各个控件的引用
    static class ViewHolder {
        TextView tv_title;
        ImageView iv_thumbnail;
        String thumbnail_url;
    }



    public boolean isBusy() {
        return mBusy;
    }

    public void setBusy(boolean busy) {
        this.mBusy = busy;
    }

}

目标:在使用TranslateAnimation移动的同时,用ScaleAnimation放大图片,在屏幕居中显示. 看TranslateAnimation的构造方法: TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) 四个参数分别是,动画开始时x座标偏移量,结束时x座标偏移量,开始时y座标偏移量,结束时y座标偏移量,单位像素,有一点搞不懂的是,为什么是float,像素还有半个? 缩放动画 ScaleAnimation(float fromX, float toX, float fromY, float toY) fromX 宽度起始缩放比例,1为原始大小 toX 宽度最终缩放比例 fromY 高度起始缩放比例,1为原始大小 toY 高度最终缩放比例 在TranslateAnimation里,偏移量还要考虑到scale的影响 下面是主要代码:

        //计算activity的size,不包括actionbar
        int activityWidth=findViewById(android.R.id.content).getWidth();
        int activityHeight=findViewById(android.R.id.content).getHeight();
        //获取图片的真实大小
        int[] size=ImageUtils.getBitmapSize(this, R.drawable.wallpaper);
        int bmpWidth=size[0];
        int bmpHeight=size[1];
        //计算目标显示尺寸
        float displayWidth=0;
        float displayHeight=0;
        // 如果图片尺寸比activity的尺寸小,直接原大小显示
        if (activityWidth > bmpWidth && activityHeight > bmpHeight) {
            displayWidth=bmpWidth;
            displayHeight=bmpHeight;
        }else{
            //缩放
            // 计算宽高比
            float target_ratio = (float) activityWidth / activityHeight;
            float real_ratio = bmpWidth / bmpHeight;
            if (real_ratio > target_ratio) {
                // 如果width太大,height太小,以activityWidth为基准
                displayWidth=activityWidth;
                displayHeight=((float)activityWidth/bmpWidth)*bmpHeight;
            } else {
                displayHeight=activityHeight;
                displayWidth=((float)activityHeight/bmpHeight)*bmpWidth;
            }
        }
        //缩放比例,等比例放大,toY=toX
        float toX=displayWidth/imageView.getWidth();

        AnimationSet animationSet = new AnimationSet(true);
        animationSet.setDuration(500);
        animationSet.setInterpolator(new AccelerateInterpolator());

        //计算左上角偏移量,有scale需要乘的1/toX
        float toXDelta=((activityWidth-displayWidth)/2.0f-imageView.getX())*(1/toX);
        float toYDelta=((activityHeight-displayHeight)/2.0f-imageView.getY())*(1/toX);
        //移动
        TranslateAnimation translateAnimation=new TranslateAnimation(0, toXDelta, 0, toYDelta);
        animationSet.addAnimation(translateAnimation);
        
        ScaleAnimation scaleAnimation=new ScaleAnimation(1.0f, toX, 1.0f, toX); 
        animationSet.addAnimation(scaleAnimation);
        animationSet.setFillAfter(true);
        imageView.startAnimation(animationSet);

使用下面的代码,

         DisplayMetrics dm = new DisplayMetrics();
         activity.getWindowManager().getDefaultDisplay().getMetrics(dm);

取到的是屏幕的尺寸,如果我们的activity不是全屏的,就会有一个StatusBar的高度,如果有ActionBar,这一部分也要占据一定的高度. 要获取整个View真实的高度,可以给xml中最外层的layout加一个id,在程序中findViewById,再取这个View的高度. 但是,这么做稍微麻烦了一点,其实Android已经给我们定义好了一个id,就是android.R.id.content,不需要在xml中定义,直接findViewById即可,再用getWidth,getHeight 另外,如果用子控件的getRootView方法取到的RootView,也会跟上面的代码一样,包括StatusBar和ActionBar