最近天气真他娘的热,炸鸡啤酒,我觉得如果不演那么什么我从来都不看的韩剧,绝对没有人喜欢这种吃法。好了,废话不多说,天这么热,我只能晚上腾出时间来写这个东东,顺便引用吉日嘎拉的博客上面的一句话"每天进步一点点"。
OK,我们这次是要把这个界面翻成Android版,大家看过我的博客都知道我一直都是拿这几个界面再弄,
什么java实战篇也是用这个界面,唉,没办法,我只有这个界面。

先上一张Android版的图,吊胃口,下面的这张是模拟器上的图,怎么样,还像个app的样子吧。

首先我们来看一下.net webService端,在webService中我新增了一个方法
[WebMethod]
public CommonResponse UserInfoModify(UserInfoEntity userInfoEntity)
{
return UserInfoBiz.GetInstance().ModifyUserInfo(userInfoEntity);
}
接下里看一下Biz层
public CommonResponse ModifyUserInfo(UserInfoEntity userInfoEntity)
{
try
{
int suc = UserInfoMngDAL.GetInstance().ModifyUserInfo(userInfoEntity);
if (suc > 0)
{
return new CommonResponse() { IsSuccess = true };
}
return new CommonResponse() { IsSuccess = false, ErrorMessage = SaveFailed };
}
catch (Exception ex)
{
return new CommonResponse() { IsSuccess = false, ErrorMessage = ex.Message };
}
}
最后看一下DAL层
public int ModifyUserInfo(UserInfoEntity userInfoEntity)
{
using (BonusEntities bonusEntities = new BonusEntities())
{
if (bonusEntities.UerInfo.Any(u => u.UseNo == userInfoEntity.UserNo)) //has existed
{
UerInfo uerInfoModify = bonusEntities.UerInfo.SingleOrDefault(u => u.UseNo == userInfoEntity.UserNo);
uerInfoModify.Name = userInfoEntity.UserName;
uerInfoModify.Sex = userInfoEntity.UserSex == "男" ? "1" : "2";
uerInfoModify.Age = userInfoEntity.UserAge;
uerInfoModify.Temper = userInfoEntity.Temper;
uerInfoModify.BirthDay = DateTime.Parse(string.Concat(userInfoEntity.BirthDay, " 00:00:01"));
if (!string.IsNullOrWhiteSpace(userInfoEntity.UserPhoto))
{
uerInfoModify.Photo = Convert.FromBase64String(userInfoEntity.UserPhoto);
}
}
else
{
UerInfo uerInfo = new UerInfo();
uerInfo.UseNo = userInfoEntity.UserNo;
uerInfo.Name = userInfoEntity.UserName;
uerInfo.Sex = userInfoEntity.UserSex == "男" ? "1" : "2";
uerInfo.Age = userInfoEntity.UserAge;
uerInfo.Temper = userInfoEntity.Temper;
uerInfo.BirthDay = DateTime.Parse(string.Concat(userInfoEntity.BirthDay, " 00:00:01"));
if (!string.IsNullOrWhiteSpace(userInfoEntity.UserPhoto))
{
uerInfo.Photo = Convert.FromBase64String(userInfoEntity.UserPhoto);
}
bonusEntities.UerInfo.Add(uerInfo);
}
return bonusEntities.SaveChanges();
}
}
非常的简单,如果存在就是修改,否则是新建。在这里需要注意的是下面这句
uerInfo.Photo = Convert.FromBase64String()
这个解释的话先看一下我们的EF实体

我们的Photo是byte[]类型,因为Ksoap是无法传递byte[]的,所以在android客户端,我们要先将byte[]编码成string,然后在.net wenservice端再反编码。好了,这里就是webservice端。
接着就到了我们的android客户端了,他才是我们这篇文章的重头戏。先上一张真机上的图,开启笔记本wifi,手机连接wifi,更换代码中的IP,OK,运行

就是这张图,大家可能会问,那右边的那个东西是什么,是鸟的翅膀吗,不是,是一个人

这边的功能是如果用户勾选checkBox,则保存的时候会将图片一并提交webservice去做保存。
图也看了,那么先看一下页面布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent">
<HorizontalScrollView android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:scrollbarAlwaysDrawHorizontalTrack="false">
<TableLayout android:layout_width="wrap_content"
android:layout_height="wrap_content" android:background="@color/blue1"
android:stretchColumns="0">
<TableRow>
<TableLayout android:layout_width="wrap_content"
android:layout_height="wrap_content" android:stretchColumns="1"
android:padding="3dp" android:layout_column="0"
android:layout_margin="1dp" android:background="@color/teal">
<TableRow>
<TextView android:text="@string/userName"
android:gravity="right" android:textSize="8pt"></TextView>
<EditText android:id="@+id/txtUserName"
android:drawableLeft="@drawable/userhint" android:hint="@string/hintInputUserName"
android:textColorHint="@color/hintColor" android:width="200dp"
android:singleLine="true" android:maxLength="25"></EditText>
</TableRow>
<TableRow>
<TextView android:text="@string/userSex"
android:layout_gravity="center_vertical" android:textSize="8pt">
</TextView>
<Spinner android:id="@+id/cmbUserSex"
android:layout_width="fill_parent" android:layout_height="wrap_content"></Spinner>
</TableRow>
<TableRow>
<TextView android:text="@string/userAge" android:gravity="right"
android:textSize="8pt"></TextView>
<EditText android:id="@+id/txtUserAge" android:editable="false"></EditText>
</TableRow>
<TableRow>
<TextView android:text="@string/userBirthDay"
android:gravity="right" android:textSize="8pt"
android:layout_gravity="center_vertical"></TextView>
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content" android:orientation="horizontal">
<EditText android:id="@+id/txtUserBirthDay"
android:editable="false"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:width="140dp" android:hint="@string/hintChooseBirth"
android:textColorHint="@color/hintColor" android:drawableLeft="@drawable/calander"
android:singleLine="true"></EditText>
<Button android:id="@+id/btnChoose" android:layout_width="80dp"
android:layout_height="45dp" android:drawableLeft="@drawable/pencil"
android:layout_gravity="center_vertical" android:text="@string/btnChoose"
android:textStyle="bold"></Button>
</LinearLayout>
</TableRow>
<TableRow>
<TextView android:text="@string/userTemper"
android:gravity="right" android:textSize="8pt"
android:layout_gravity="center_vertical"></TextView>
<RadioGroup android:id="@+id/radioGroup"
android:contentDescription="脾气" android:layout_width="wrap_content"
android:orientation="horizontal" android:layout_height="wrap_content">
<RadioButton android:layout_width="wrap_content"
android:textColor="@color/red1" android:layout_height="wrap_content"
android:id="@+id/radioTemper1" android:text="@string/userTemper1"
android:checked="true"></RadioButton>
<RadioButton android:layout_width="wrap_content"
android:textColor="@color/red1" android:layout_height="wrap_content"
android:id="@+id/radioTemper2" android:text="@string/userTemper2"></RadioButton>
</RadioGroup>
</TableRow>
<TableRow>
<LinearLayout android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_span="2">
<Button
android:id="@+id/btnSave"
android:text="@string/btnSave"
android:textColor="@color/blue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textStyle="bold"></Button>
<Button android:text="@string/btnCancelText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/btnCancel"
android:textColor="@color/blue" android:textStyle="bold"
></Button>
</LinearLayout>
</TableRow>
</TableLayout>
<TableLayout android:layout_width="wrap_content"
android:background="@color/teal" android:layout_margin="1dp"
android:layout_height="wrap_content" android:layout_column="1">
<TableRow>
<ImageView android:id="@+id/imgUserPhoto"
android:background="@color/imgBg" android:layout_marginLeft="10dp"
android:layout_marginTop="5dp" android:layout_marginRight="5dp"
android:layout_width="160dp" android:layout_height="240dp"
android:src="@drawable/deaultphoto" android:scaleType="fitCenter"
android:layout_span="2" />
</TableRow>
<TableRow>
<CheckBox android:id="@+id/chkChoosePhoto"
android:layout_marginLeft="10dp" android:layout_column="0"></CheckBox>
<Button android:id="@+id/btnBrowser" android:text="@string/btnChoosePhoto"
android:textStyle="bold" android:layout_width="150dp"
android:layout_height="45dp" android:layout_marginTop="1dp"
android:textColor="@color/blue" android:layout_column="1"></Button>
</TableRow>
</TableLayout>
</TableRow>
</TableLayout>
</HorizontalScrollView>
</LinearLayout>
布局的话主要有以下几点,第一,这个布局总体采用TableLayout,因为我们的界面的宽度的缘故,所以我在table的外层加了个HorizontalScrollView,用来左右滚动。第二,这个界面的布局是采用左右各占一列, 在列中又嵌套了Table。做过Silverlight的人都知道,Grid布局是很好用的,这个和Silverlight的Grid布局有点像。布局的话其实都很简单,也没啥看点。
在界面中,大家都看到了有个下拉列表样子的东西,那是什么,是ComboBox?我靠,你以为这是在做Silverlight呢。这个是Android中的Spinner控件。我们来看一下他是如何加载下拉数据和响应事件的。
首先我们在string.xml文件中新增了一个string-array资源,用来加载到下拉列表。

在代码中,我们会构造一个Spinner加载数据的一个适配器,如下
private void InitData() {
// ArrayAdapter<String> adapter;
// adapter = new ArrayAdapter<String>(this,
// android.R.layout.simple_spinner_item, sexArray);
final ArrayAdapter<?> adapter = ArrayAdapter.createFromResource(this,
R.array.sexArray, android.R.layout.simple_spinner_item);
adapter
.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spnUserSex.setAdapter(adapter);
// spnUserSex.setOnItemSelectedListener(new OnItemSelectedListener() {
// public void onItemSelected(AdapterView<?> arg0, View arg1,
// int arg2, long arg3) {
// String selectedItem = adapter.getItem(arg2).toString();
// }
//
// public void onNothingSelected(AdapterView<?> arg0) {
// }
// });
}
看到了吧,那句R.Array.SexArray就是从资源文件取出性别集合的。
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
这句是表示我们的下拉列表展示简单的项(只有文子字),如果你想让你的下拉列表更生动,你可以去加载模版,比如在男选项前面放一个男人头像,女选项前面放一个女头像。这个其实和Silverlight的ComboBox的模版类似。OK,最后给Spinner设置适配器。我注释的上面部分是当不从资源文件加载数据的时候的代码,下面部分是下拉事件响应的代码。我们看一下下拉效果

此时,就可以在界面选择你想要的结果。

好了,那我们接下来看这个出生日期,为什么要先看出生日期呢,因为年龄是根据出生日期算出来的,难道您刚才没注意那个年龄的文本框是设置为不能编辑的吗(android:editable="false")。
出生日期后面的那个选择按钮完成的功能是弹出日期选择界面。看代码
this.btnChoose.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
Calendar calendar = Calendar.getInstance();
DatePickerDialog dialog = new DatePickerDialog(owner,
new DatePickerDialog.OnDateSetListener() {
public void onDateSet(DatePicker dp, int year,
int month, int dayOfMonth) {
txtBirthDay.setText(year + "-" + month + "-"
+ dayOfMonth);
SimpleDateFormat df = new SimpleDateFormat();
df.applyPattern("yyyy-MM-dd hh:mm:ss");
try {
Date dt = df.parse(year + "-" + month + "-"
+ dayOfMonth + " 00:00:01");
int age = new Date().getYear()
- dt.getYear();
txtAge.setText(String.valueOf(age));
} catch (ParseException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}, calendar.get(Calendar.YEAR), calendar
.get(Calendar.MONTH), calendar
.get(Calendar.DAY_OF_MONTH));
dialog.show();
}
});
看到了吧,我们直接弹出android内置的Dialog(DatePickerDialog),看一下效果

在他的日期设置事件(onDateSet)中,我们拿到选择的日期,先赋给出生日期文本框,然后再用当前的年份减去选择的年份,算出来就是年龄,把年龄赋给年龄文本框。如果你想设置初始化的日期的话,需要注意他DatePickerDialog的构造函数最后三个参数,来自API的解释

OK,日期看完之后,就是右边的图片了,首先我们要知道图片从哪里来,当然是从手机里来,是个人都知道。我们看一下点击浏览按钮做的事情。
this.btnBrowser.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
Intent intent = new Intent();
intent.setType("p_w_picpath/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(intent, 1);
}
});
看到了吧,启动手机的图片照片搜索界面,如下

选择一张照片,图片就会显示到图片框中,如下

那么图片是怎么显示到图片框中的,第一步,我们要重写当前Activity的onActivityResult方法。
protected void onActivityResult(int requestCode, int resultCode,
android.content.Intent data) {
if (resultCode == RESULT_OK) {
Uri uri = data.getData();
ContentResolver contentResolver = this.getContentResolver();
try {
Bitmap bitmap = BitmapFactory.decodeStream(contentResolver
.openInputStream(uri));
imgUserPhoto.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
}
}
super.onActivityResult(requestCode, resultCode, data);
}
我们拿到图片的资源地址后,转化成Bitmap,赋给图片框。在这里图片框有多种显示拉伸方式,我就不多说了,自己查吧。OK,图片也显示完了,我们看最后的保存。
在看保存之前,我们先看一下取消
this.btnCancel.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
final AlertDialog.Builder builder = new AlertDialog.Builder(
owner);
builder.setIcon(R.drawable.info);
builder.setTitle(R.string.titleSystemCodeModifyName);
builder.setMessage("您确定要退出修改吗?");
builder.setPositiveButton(R.string.btnSure, null);
builder.setNegativeButton(R.string.btnCancelText, null);
final AlertDialog dialog = builder.create();
dialog.show();
dialog.getButton(AlertDialog.BUTTON_POSITIVE)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userinfomanage.this.setResult(RESULT_OK);
userinfomanage.this.finish();
}
});
}
});
取消这个很简单,就是构造一个弹出框,点击确定关闭当前Activity,点击取消,不关闭界面

OK,最后我们看一下我们的save。
this.btnSave.setOnClickListener((new OnClickListener() {
public void onClick(View view) {
if (!CheckUserInput())
return;
UserInfoEntity userInfoEntity = GetUserInfoEntity();
SoapObject soapObject = ModifyUserInfoEntty(userInfoEntity);
Boolean isSuccess = Boolean.valueOf(soapObject.getProperty(
"IsSuccess").toString());
if (isSuccess) {
ShowMessage(R.string.SaveSuccess);
} else {
String errorMsg = soapObject.getProperty("ErrorMessage")
.toString();
ShowMessage(errorMsg);
}
}
}));
首先是check,如下,很简单
private Boolean CheckUserInput() {
String userName = this.txtUserName.getText().toString().trim();
if (userName.length() == 0) {
this.ShowMessage("姓名不能为空!");
this.txtUserName.requestFocus();
return false;
}
String birthDay = this.txtBirthDay.getText().toString().trim();
if (birthDay.length() == 0) {
this.ShowMessage("出生日期不能为空!");
this.btnBrowser.requestFocus();
return false;
}
return true;
}
接着是拿到要保存的实体GetUserInfoEntity
private UserInfoEntity GetUserInfoEntity() {
UserInfoEntity userInfoEntity = new UserInfoEntity();
userInfoEntity.setProperty(1, txtUserName.getText().toString());
userInfoEntity.setProperty(0, userNo);
userInfoEntity.setProperty(2, spnUserSex.getSelectedItem());
userInfoEntity.setProperty(3, txtAge.getText());
userInfoEntity.setProperty(4, txtBirthDay.getText());
userInfoEntity.setProperty(5, radiobtnTemper1.isChecked() ? "1" : "2");
if (chkPhoto.isChecked()) {
String strByte = Base64.encode(GetImageByteArray());
userInfoEntity.setProperty(6, strByte);
}
return userInfoEntity;
}
需要注意的是这里Base64.encode(GetImageByteArray()),这个就是刚才说的KSoap不支持直接传byte[],而是要转码。GetImageByteArray这个方法是将图片框中的图片转化成byte[]。
private byte[] GetImageByteArray() {
byte[] compressData = null;
imgUserPhoto.setDrawingCacheEnabled(true);
Bitmap bmp = Bitmap.createBitmap(imgUserPhoto.getDrawingCache());
imgUserPhoto.setDrawingCacheEnabled(false);
if (bmp != null) {
compressData = GetByteArrayByBitmap(bmp);
}
return compressData;
}
private byte[] GetByteArrayByBitmap(Bitmap bmp) {
byte[] compressData = null;
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.JPEG, 100, byteOutputStream);
compressData = byteOutputStream.toByteArray();
try {
byteOutputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return compressData;
}
这都是固定写法,不多做解释。OK,我们看一下实体的定义,免得看得人摸不着头脑
public class UserInfoEntity implements KvmSerializable {
private String UserNo;
private String UserName;
private String UserSex;
private int UserAge;
private String BirthDay;
private String Temper;
private String UserPhoto;
@Override
public Object getProperty(int arg0) {
// TODO Auto-generated method stub
Object property = null;
switch (arg0) {
case 0:
property = this.UserNo;
break;
case 1:
property = this.UserName;
break;
case 2:
property = this.UserSex;
break;
case 3:
property = this.UserAge;
break;
case 4:
property = this.BirthDay;
break;
case 5:
property = this.Temper;
break;
case 6:
property = this.UserPhoto;
break;
default:
break;
}
return property;
}
@Override
public int getPropertyCount() {
// TODO Auto-generated method stub
return 7;
}
@Override
public void getPropertyInfo(int arg0, Hashtable arg1, PropertyInfo arg2) {
// TODO Auto-generated method stub
switch (arg0) {
case 0:
arg2.type = PropertyInfo.STRING_CLASS;
arg2.name = "UserNo";
break;
case 1:
arg2.type = PropertyInfo.STRING_CLASS;
arg2.name = "UserName";
break;
case 2:
arg2.type = PropertyInfo.STRING_CLASS;
arg2.name = "UserSex";
break;
case 3:
arg2.type = PropertyInfo.INTEGER_CLASS;
arg2.name = "UserAge";
break;
case 4:
arg2.type = PropertyInfo.STRING_CLASS;
arg2.name = "BirthDay";
break;
case 5:
arg2.type = PropertyInfo.STRING_CLASS;
arg2.name = "Temper";
break;
case 6:
arg2.type = PropertyInfo.STRING_CLASS;
arg2.name = "UserPhoto";
break;
default:
break;
}
}
@Override
public void setProperty(int arg0, Object arg1) {
// TODO Auto-generated method stub
if (arg1 == null)
return;
switch (arg0) {
case 0:
this.UserNo = arg1.toString();
break;
case 1:
this.UserName = arg1.toString();
break;
case 2:
this.UserSex = arg1.toString();
break;
case 3:
this.UserAge = Integer.parseInt(arg1.toString());
break;
case 4:
this.BirthDay = arg1.toString();
break;
case 5:
this.Temper = arg1.toString();
break;
case 6:
this.UserPhoto = arg1.toString();
default:
break;
}
}
}
和.net WebServce端是对应的。OK,最后我们看一下保存(ModifyUserInfoEntty)的代码。
private SoapObject ModifyUserInfoEntty(UserInfoEntity userInfoEntity) {
SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
PropertyInfo pi = new PropertyInfo();
pi.setName("userInfoEntity");
pi.setValue(userInfoEntity);
pi.setType(userInfoEntity.getClass());
request.addProperty(pi);
SoapSerializationEnvelope soapEnvelope = new SoapSerializationEnvelope(
SoapEnvelope.VER11);
soapEnvelope.dotNet = true;
HttpTransportSE httpTS = new HttpTransportSE(URL);
soapEnvelope.bodyOut = httpTS;
soapEnvelope.setOutputSoapObject(request);// 设置请求参数
soapEnvelope.addMapping(NAMESPACE, "UserInfoEntity", userInfoEntity
.getClass());
new MarshalBase64().register(soapEnvelope);
try {
httpTS.call(SOAP_ACTION, soapEnvelope);
} catch (IOException e) {
// TODO Auto-generated catch block
this.ShowMessage(e.getMessage());
// e.printStackTrace();
} catch (XmlPullParserException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
SoapObject result = null;
try {
result = (SoapObject) soapEnvelope.getResponse();
} catch (SoapFault e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
这里需要注意的是new MarshalBase64().register(soapEnvelope);这是要告诉soap 信使message中包含有Base64转过的byte[]。OK,最后,我们鼓起勇气点击save。

走起,见证奇迹的时刻

yeah,成功了,图片是否成功我们需要借助C#版的程序看一下,成功了。

最后,哥们这博客可真是货真价实,中兴U880S测试机。

评价一下你不会吃亏,评价一下你不会上当,你评的越多,我写的越多。他大舅他二舅都是他舅,高桌子低板凳都是木头,进来的都是干这一行的,给个评价吧。