我们使用ViewHolder时,把每一个item的子View控件对象都放在ViewHolder中,当第一次创建convertView对象时,便把这些item的子View控件对象findViewById实例化出来并保存到ViewHolder的对象中。然后用convertView的setTag将viewHolder对象设置到Tag中, 当以后加载ListView的item时便可以直接从Tag中取出复用ViewHolder对象中的,不需要再findViewById找item的子控件对象了。这样便大大提高了性能。
但是,某些情况下,这种优化技术便出现了一种问题,举个例子,当我们由于项目需要,需要在ListView中用到CheckBox时,那么,就会出现这样的情况:
假设一个ListView有10个Item,当我选择第一个Item的CheckBox之后,往下滑动到原先不可见的位置时就会发现,还有其他的CheckBox也被选中了,这样就造成了很不好的用户体验。
先上出现了这种问题的代码:
布局文件:
activity_listview.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</LinearLayout>
item_list.xml 一个CheckBox和一个TextView,设为100dp高的原因是,必须有未显示的Item,ListView才会去复用View,如果一个屏幕显示了全部的Item,那么就不回复用了。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="100dp">
<CheckBox
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/textView"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
</LinearLayout>
MyAdapter.java
package cn.zmit.myapplication;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List;
/**
* Created by Administrator on 2016/3/23.
*/
public class MyAdapter extends BaseAdapter {
private List<String> list;
private Context context;
public MyAdapter(List<String> lists, Context contexts) {
list = lists;
context = contexts;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = View.inflate(context, R.layout.item_list, null);
holder.textView = (TextView) convertView.findViewById(R.id.textView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.textView.setText(list.get(position));
return convertView;
}
private class ViewHolder {
TextView textView;
}
}
ListVeiwDemo.java
package cn.zmit.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2016/3/18.
*/
public class ListViewDemo extends Activity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_listview);
listView= (ListView) findViewById(R.id.listview);
List<String> list=new ArrayList<>();
for(int i=0;i<10;i++){
list.add(i+"");
}
MyAdapter adapter=new MyAdapter(list,this);
listView.setAdapter(adapter);
}
}
非常正常的一个ListView实现过程,贴代码的原因是担心有初学者不懂我在说什么。大家可以把代码拷贝一下或者自己编写看一下效果。
接下来说一下怎么解决!!!!
首先,定义一个Map,
Map<Integer,View>map=new HashMap<>();
当convertView为null时,在convertView和子控件加载完成后,将convertView加入map:
map.put(position,convertView);
我们想一想,这时候,在前面判断的时候,还应该是
if (convertView == null)
这样吗?当然不是,换成这样:
if (map.get(position)==null)
也就是说,只要你这个Item没被加载过,就给我重新加载!
然后在下面的else里这样写:
else {
convertView=map.get(position);
holder = (ViewHolder) convertView.getTag();
}
假设map里有这个View,就拿出来用。
全部解决完问题的Adapter的getView部分代码为:
Map<Integer,View>map=new HashMap<>();
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (map.get(position)==null) {
holder = new ViewHolder();
convertView = View.inflate(context, R.layout.item_list, null);
holder.textView = (TextView) convertView.findViewById(R.id.textView);
map.put(position,convertView);
convertView.setTag(holder);
} else {
convertView=map.get(position);
holder = (ViewHolder) convertView.getTag();
}
holder.textView.setText(list.get(position));
return convertView;
}
首先自定义一个LinearLayout
MyLinearLayout:
package cn.zmit.myapplication;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.text.BoringLayout;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
/**
* Created by kyle on 2016/3/14.
*/
public class MyLinearLayout extends LinearLayout {
private int startX;//初始位置X坐标
private Paint mPaint;
private int moveX;//移动时不断变化的X坐标
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.parseColor("#FFFFFF"));//画笔颜色
mPaint.setStyle(Paint.Style.FILL);//画笔样式(填充内部)
}
public MyLinearLayout(Context context) {
super(context, null);
}
/***
* 开始画圆
*
* @param canvas
*/
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
startX = getWidth() / 8;//最开始位置的X坐标
canvas.save();//保存
canvas.drawCircle(startX + moveX, getHeight() - 15, 5, mPaint);
canvas.restore();//取出
}
/***
* 当手指滑动时调用这个方法(在viewpager的onPageScrolled方法调用)
*
* @param position
* @param Offset
*/
public void changed(int position, float Offset) {
moveX = (int) (getWidth() / 4 * Offset + position * getWidth() / 4);
invalidate();//刷新
}
}
既然用到ViewPager,当然需要fragment;
MyFragment.java:
package cn.zmit.myapplication;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
/**
* Created by Administrator on 2016/3/14.
*/
public class MyFragment extends Fragment {
public static final String TITLE = "title";
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
TextView textView = new TextView(getActivity());
textView.setText(getArguments().getString(TITLE));
textView.setTextColor(Color.parseColor("#000000"));
textView.setGravity(Gravity.CENTER);
return textView;
}
public static MyFragment getInstance(String title) {
Bundle pBundle = new Bundle();
pBundle.putString(TITLE, title);
MyFragment fragment = new MyFragment();
fragment.setArguments(pBundle);
return fragment;
}
}
MainActivity.java:
package cn.zmit.myapplication;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.Window;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MainActivity extends FragmentActivity {
private MyLinearLayout mLinearLayout;
private ViewPager mViewpager;
private FragmentStatePagerAdapter adapter;
public List<String> lists = Arrays.asList("推荐", "排行", "分类","我的");
private List<MyFragment> list = new ArrayList<>();
private void assignViews() {
mLinearLayout = (MyLinearLayout) findViewById(R.id.linearLayout);
mViewpager = (ViewPager) findViewById(R.id.viewpager);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
assignViews();
initData();
}
private void initData() {
for (String title : lists) {
MyFragment fragment = MyFragment.getInstance(title);
list.add(fragment);
}
adapter = new FragmentStatePagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return list.get(position);
}
@Override
public int getCount() {
return list.size();
}
};
mViewpager.setAdapter(adapter);
mViewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
mLinearLayout.changed(position, positionOffset);
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
}
好了,接下来开始讲一下原理。
这个例子最重要的地方,大家都能知道,在于自定义的LinearLayout,我们先从这里说起吧。
首先,
private int startX;//初始位置X坐标
private Paint mPaint;
private int moveX;//移动时不断变化的X坐标
startX为初始化原点的x坐标
maveX为当我们滑动ViewPager时动态改变的X数值。
然后,在构造方法中,初始画笔mPaint,设置颜色等属性。
接着,在dispatchDraw方法中执行画图功能,这个方法系统自动调用,主要是分发给子组件进行绘制。
代码解析:
startX = getWidth() / 8;//最开始位置的X坐标
初始化startX为屏幕的1/8,因为布局被分为4块,而原点又要位于每个模块的中间,所以初始的X应该为屏幕宽度的1/8。
canvas.save();//保存
canvas.drawCircle(startX + moveX, getHeight() - 15, 5, mPaint);
canvas.restore();//取出
sava和restore方法用于保存画布画图之前的状态和取出之前的状态,绘图时最好带上,这里不说了,有兴趣的同学可以去了解下。
drawCircle方法,顾名思义,画圆,第一个参数代表当前X坐标;第二个参数是高度,全程固定为布局的高度减去15,
大概在字和底部的中间。这个数值可以自己改,改到自己喜欢为止;第三个参数是半径,这里设为5,同理,可以根据喜好改;第四个参数是画笔,之前初始化完成了,这里直接加进去。
先看一下效果:
1
最后,重头戏来了,大家还记得在之前的ViewPager的addOnPageChangeListener里的onPageScrolled中做了什么吗?没错,在里面调用了一个方法:changed,让我们看一下,这个方法里做了什么
public void changed(int position, float Offset) {
moveX = (int) (getWidth() / 4 * Offset + position * getWidth() / 4);
invalidate();//刷新
}
看里面内容,将moveX的值变成了getWidth() / 4 * Offset + position * getWidth() / 4。
这到底是多少呢?先介绍一下两个参数,Offset,即滑动的宽度的百分比,比如我手指向左滑动,那么ViewPager的当前fragment也会随着手指往左边移动,当前fragment被隐藏的宽度占当前fragment的总宽度的百分比,即为Offset。Position就是当前fragment为第几个fragment,第一个的话,postion就为0,因为从0开始嘛!(注意,手指往左滑,position为当前position,手指往右滑,position为当前position-1)
介绍完参数后,开始说一下这个moveX到底变成了多少。
getWidth/4,屏幕宽度的1/4,即一个模块的宽度,乘以Offset,即随着手指滑动,使moveX的数值一直变大,直至正好为一个模块的宽,也就是从一个模块,滑到了另一个模块。听着已经可以了,但是不要忘记,Offset每次滑动后,都会变成0,也就是说,假设就这样完事的话,你只能将小圆点从第一个模块移动到第二个模块,无论你怎么滑。
好了,接着,后面还加了position * getWidth() / 4,即当前fragment的position乘以当前模块的宽,这样就解决了只能滑到第二个模块的问题,因为假设当前fragment为第二个,position为1,原点位于模块2,即“排行”,X的数值为X=startX+moveX;
startX=getWidth() / 8 ;
moveX=getWidth() / 4 * Offset + 1* getWidth() / 4;
当我们没动时,X的数值等于”排行”两字中间的X值,
X=getWidth() / 8+1* getWidth() / 4;
当我们往左滑动,Offset不断变大,直至1,position为1
X=getWidth() / 8+getWidth() / 4 +1* getWidth() / 4;
正好比没滑动时大一个模块,即滑到了第三个模块”分类”
当我们往右滑动,Offset不断变大,直至1,position为0
X=getWidth() / 8+getWidth() / 4 +0* getWidth() / 4;
正好比没滑动时小一个模块,即滑到了第一个模块”推荐”
效果图由于条件限制,没法录制gif,所以大家可以自己把代码运行一下看效果。
总结:这个例子主要是运用了viewGroup的画图功能,自定义了一个带圆点的LinearLayout,随后运用ViewPager的滑动参数,动态改变圆点位置。主要在于了解viewPager的运行机制,还有需要了解画笔的使用。
原文来自 :http://blog.it985.com/15824.html
android从选择图片有两种方法,但是返回值确不同,本文将指导大家如何统一这两种方式的返回值。
//关键代码
@Event(R.id.btnPhoto)
private void onBtnPhotoClicked(View view) {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, Config.Constants.CODE_PICK_IMAGE_FROM_PHOTO);
}
@Event(R.id.btnCamera)
private void onBtnCameraClicked(View view) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, Config.Constants.CODE_PICK_IMAGE_FROM_CAMERA);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case Config.Constants.CODE_PICK_IMAGE_FROM_CAMERA:
if (data != null && data.hasExtra("data")) {
Bitmap bitmap = data.getParcelableExtra("data");
bitmap = BitmapUtil.scale(bitmap, 640.0f / bitmap.getWidth());
try {
File path = new File(((MyApplication) getApplication()).appPath, DateUtil.format(new Date(), "yyyyMMddHHmmss") + ".jpg");
FileOutputStream outputStream = new FileOutputStream(path);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.close();
Intent intent = new Intent();
intent.putExtra("path", path.getAbsolutePath());
setResult(RESULT_OK, intent);
} catch (IOException e) {
e.printStackTrace();
}
}
finish();
break;
case Config.Constants.CODE_PICK_IMAGE_FROM_PHOTO:
if(data != null){
Uri uri = data.getData();
Bitmap bitmap;
ContentResolver contentResolver = getContentResolver();
try {
bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri);
bitmap = BitmapUtil.scale(bitmap, 640.0f / bitmap.getWidth());
File path = new File(((MyApplication) getApplication()).appPath, DateUtil.format(new Date(), "yyyyMMddHHmmss") + ".jpg");
FileOutputStream outputStream = new FileOutputStream(path);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.close();
Intent intent = new Intent();
intent.putExtra("path", path.getAbsolutePath());
setResult(RESULT_OK, intent);
} catch (IOException e) {
e.printStackTrace();
}
}
finish();
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
}
//BitmapUtil.java
public static Bitmap scale(Bitmap bitmap, float scale) {
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
//MyApplication
public File appPath;
@Override
public void onCreate() {
super.onCreate();
//创建目录
appPath = new File(Environment.getExternalStorageDirectory(), getPackageName());
if (!appPath.isDirectory()) {
appPath.mkdir();
}
}
经过统一处理之后,返回值均为图片的绝对路径地址。
华为P9什么时候出?
P9发布会是4月6日。价格方面标准版 3088 元,高配版 3688 元
华为P9手机配置怎么样?
华为“??”图案正暗示其双镜头的设计。据悉将配备 5.2 ? 1080p 屏幕,麒麟 950,3G / 4G 内存,32GB 存储,1200W 双摄像头。价格方面标准版 3088 元,高配版 3688 元
华为P9采用双摄像头设计,并配备华为自主研发的传感器组合技术,支持重调焦距、模拟光圈调整、滤波镜应用等。由于其摄影光学系统是由德国著名光学品牌徕卡来调教,所以还会在机身上标记徕卡的“可乐标”
有网友曝光了一组P9以及保护套的渲染图,从图中来看,和之前曝光的一样,P9采用了背后双摄像头,配备双LED闪光灯。此外,指纹识别模块也同样设计在了背部。
手机侧面采用了金属中框,视觉上看起来很薄,背壳则是全金属材质。
android中apk的下载与自动安装这个虽然不推荐这样来做但有些朋友一定要问我如何实现了,这里整理了一些方法希望对各位有一些帮助了,具体如下。手机中文件的下载分为后台自动下载和前台下载,我总结了这两种下的实现代码,其中前台下载并实现下载进度条的实现。
第一种:后台下载
/**
* 后台在下面一个Apk 下载完成后返回下载好的文件
*
* @param httpUrl
* @return
*/
private File downFile(final String httpUrl) {
new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL(httpUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
FileOutputStream fileOutputStream = null;
InputStream inputStream;
if (connection.getResponseCode() == 200) {
inputStream = connection.getInputStream();
if (inputStream != null) {
file = getFile(httpUrl);
fileOutputStream = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int length = 0;
while ((length = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, length);
}
fileOutputStream.close();
fileOutputStream.flush();
}
inputStream.close();
}
//下载完成
//安装
installApk();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
return file;
}
/**
* 根据传过来url创建文件
*
*/
private File getFile(String url) {
File files = new File(Environment.getExternalStorageDirectory().getAbsoluteFile(), getFilePath(url));
return files;
}
/**
* 截取出url后面的apk的文件名
*
* @param url
* @return
*/
private String getFilePath(String url) {
return url.substring(url.lastIndexOf("/"), url.length());
}
/**
* 安装APK
*/
private void installApk() {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);
}
第二种
//下载apk程序代码
protected File downLoadFile(String httpUrl) {
// TODO Auto-generated method stub
final String fileName = "updata.apk";
File tmpFile = new File("/sdcard/update");
if (!tmpFile.exists()) {
tmpFile.mkdir();
}
final File file = new File("/sdcard/update/" + fileName);
try {
URL url = new URL(httpUrl);
try {
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
InputStream is = conn.getInputStream();
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[256];
conn.connect();
double count = 0;
if (conn.getResponseCode() >= 400) {
Toast.makeText(Main.this, "连接超时", Toast.LENGTH_SHORT)
.show();
} else {
while (count <= 100) {
if (is != null) {
int numRead = is.read(buf);
if (numRead <= 0) {
break;
} else {
fos.write(buf, 0, numRead);
}
} else {
break;
}
}
}
conn.disconnect();
fos.close();
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return file;
}
//打开APK程序代码
private void openFile(File file) {
// TODO Auto-generated method stub
Log.e("OpenFile", file.getName());
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
startActivity(intent);
}
上面这种方式直接调用downFile方法,参数传个URL地址就可以了
第三种:
private String url="http://----------------------.apk";
private File file;
private ProgressBar pb;
private TextView tv;
private int fileSize;
private int downLoadFileSize;
private String filename;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {//定义一个Handler,用于处理下载线程与UI间通讯
if (!Thread.currentThread().isInterrupted()) {
switch (msg.what) {
case 0:
pb.setMax(fileSize);
case 1:
pb.setProgress(downLoadFileSize);
int result = downLoadFileSize * 100 / fileSize;
tv.setText(result + "%");
break;
case 2:
finish();
openFile(file);
break;
case -1:
String error = msg.getData().getString("error");
Toast.makeText(DownLoadActivity.this, error, Toast.LENGTH_SHORT).show();
break;
}
}
super.handleMessage(msg);
}
};
new Thread() {
public void run() {
try {
down_file(url, "/sdcard/");
//下载文件,参数:第一个URL,第二个存放路径
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
public void down_file(String url, String path) throws IOException {//下载函数
filename = url.substring(url.lastIndexOf("/") + 1);//获取文件名
URL myURL = new URL(url);
URLConnection conn = myURL.openConnection();
conn.connect();
InputStream is = conn.getInputStream();
this.fileSize = conn.getContentLength();//根据响应获取文件大小
if (this.fileSize <= 0) throw new RuntimeException("无法获知文件大小 ");
if (is == null) throw new RuntimeException("stream is null");
FileOutputStream fos = new FileOutputStream(path + filename);//把数据存入路径+文件名
byte buf[] = new byte[1024];
downLoadFileSize = 0;
sendMsg(0);
do {
//循环读取
int numread = is.read(buf);
if (numread == -1) {
break;
}
fos.write(buf, 0, numread);
downLoadFileSize += numread;
sendMsg(1);//更新进度条
} while (true);
file = new File(path + filename);
sendMsg(2);//通知下载完成
try {
is.close();
} catch (Exception ex) {
Log.e("tag", "error: " + ex.getMessage(), ex);
}
}
private void sendMsg(int flag) {
Message msg = new Message();
msg.what = flag;
handler.sendMessage(msg);
}
//打开APK程序代码
private void openFile(File file) {
// TODO Auto-generated method stub
Log.e("OpenFile", file.getName());
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
startActivity(intent);
}