最近,做完了第一个Android端的项目《小桔灯》,其实这个的主要目的就是练手,是熟悉一下Android究竟是个什么东西,经过这个过程,也算是Android端开发有了个基本的了解吧,也就刚刚入门的水平。
抛开技术以外,APP做完主要有以下两点收获:
(1)感觉自己挺喜欢Android端的开发,有机会的话,希望能深入的做下去;
(2)Intellij 公司开发的IDE真的好用;
好了,言归正传。下面就这个APP主要的功能难点和亮点做个整理、总结。
First of all,系统的功能结构图如下:
image.png
详细功能见上图,我不再赘述了,直接说学到的功能难点了。
1 通过RecycleView实现多种布局
(1)效果
main.gif.gif(2)说明:其实这里面有5种布局:顶部轮播图、切换卡片、新闻列表、横向滑动书籍信息、每日一问。
(3)实现:这里主要是通过RecyclerView实现多种item布局,实现该功能。
- 重写onBindViewHolder中的getItemViewType()方法,这个方法会传进一个参数position表示当前是第几个Item,可以通过position拿到当前的Item对象,然后判断这个item对象需要那种视图,返回一个int类型的视图标志,最后根据视图类型初始化合适的布局。
//很重要
private List<List<Object>> mAllDatas = new ArrayList();//初始化数据
private List<Object> mDataType = new ArrayList();//布局类型
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
if (getItemViewType(position) == AppConstants.RV_TOP_NEWS) {
//to do something
}else if (getItemViewType(position) == AppConstants.RV_ARTICLE_NEWS_SMALL) {
//to do something
}else if (getItemViewType(position) == AppConstants.RV_DATE_NEWS) {
//to do something
}
}
- 然后在onCreatViewHolder中具体的为每一种类型引入其布局。
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == AppConstants.RV_TOP_NEWS) {
View view = mInflater.inflate(R.layout.top_news, parent, false);
return new TopNewsHolder(view);
} else if (viewType == AppConstants.RV_DATE_NEWS) {
View view = mInflater.inflate(R.layout.read_swipe_card, parent, false);
return new CardNewsHolder(view);
} else if (viewType == AppConstants.RV_ARTICLE_NEWS_SMALL) {
View view = mInflater.inflate(R.layout.small_news, parent, false);
return new SmallNewsHolder(view);
} else if (viewType == AppConstants.RV_GUESS_NEWS) {
View view = mInflater.inflate(R.layout.horizontal_news, parent, false);
return new HorizontalHolder(view);
} else if (viewType == AppConstants.RV_DAILYASK) {
View view = mInflater.inflate(R.layout.read_daily_ask, parent, false);
return new DailyAskHolder(view);
} else if (viewType == AppConstants.RV_DIVIDER) {
View view = mInflater.inflate(R.layout.read_recy_header, parent, false);
return new DivideHolder(view);
} else {
return null;
}
}
- 在onBindViewHolder中根据对应的ViewHolder对其控件设置数据并显示。
if (getItemViewType(position) == AppConstants.RV_TOP_NEWS) {}
else if (getItemViewType(position) == AppConstants.RV_ARTICLE_NEWS_SMALL) {
smallNewsController = new SmallNewsController(mAllDatas.get(position), mContext);
((SmallNewsHolder)holder).smallNewsView.setAdapter(smallNewsController.getAdapter());}
else if (getItemViewType(position) == AppConstants.RV_GUESS_NEWS) {
mSubAdapterCrl = new SubAdapterController(mAllDatas.get(position), mContext);
((HorizontalHolder)holder).nestListView.setAdapter(mSubAdapterCrl.getAdapter()); }
4 最后一点,也是最重要的一点就是,如果需要从网络上取数据,而且需要按照某种要求进行,那么就需要使用两个东西来存放(1)数据(2)数据类型;
private List<List<Object>> mAllDatas = new ArrayList();//初始化数据
private List<Object> mDataType = new ArrayList();//布局类型
然后,在请求网络数据的时候,要将布局的类型和数据相对应,这点很重要,例如需要显示三个新闻详情页面,那就需要在三个数据中对应三个布局都是新闻详情页
public void onSuccess(String result) {
Gson gson = new Gson();
Smallnews smallnew = gson.fromJson(result, Smallnews.class);
if (smallnew.data.detail.size() > 3) {
for (int i = 0; i < 3; i++) {
List<Object> mAllData = new ArrayList();
mAllData.add(smallnew.data.detail.get(i));
mAllDatas.add(mAllData);
mDataType.add(AppConstants.RV_ARTICLE_NEWS_SMALL);
}
} else {
for (int i = 0; i < smallnew.data.detail.size(); i++) {
List<Object> mAllData = new ArrayList();
mAllData.add(smallnew.data.detail.get(i));
mAllDatas.add(mAllData);
mDataType.add(AppConstants.RV_ARTICLE_NEWS_SMALL);
}
}
(4) 剩下的重点就是,每个Item中的布局应该如何去实现了,比如轮播图、卡片滑动等,这其实就是:按照你需要的效果去找轮子,然后想办法把轮子使用到你的车子上,可以成功的开起来,那么就可以了。当然,轮子的适配程度也直接说明了开发人员的能力。
2 Viewpager + PullToRefreshListView 实现页面切换,上拉下拉刷新
(1)效果
page2.gif2 说明:分类展示用户发布的有约。
3 需要的轮子:
- Viewpager :控制页面切换
- PullToRefreshListView :控制刷新
4 实现思路:
将需要展示的页面以Fragment 的形式填充到页面ViewPager中。
- 自定义每个显示的Item,布局即为需要展示的页面布局,继承于Fragment。将这些Fragment存入到ArrayList中;
- 将需要展示Item填充至Viewpager的Adapter中,设置相应的监听事件等属性;
- 更改Viewpager的样式,修改或重写其引用的样式文件;
- 覆写PullToRefreshListView的上拉和下拉刷新的方法,实现上拉下拉刷新。
3 评论功能,包括二级评论
(1)效果
comment.gif(2)说明:书籍的评论,包括二级评论。
(3)实现:
- 布局:
这一块主要的难点是布局的嵌套,主要是以下几个布局的嵌套。
(1)像主评论布局中嵌入一级评论布局,在向一级评论布局中嵌入二级评论(多个)。
(2)点击评论时,下方弹窗要动态的弹出和落下。
首先就是一级布局,有一个ListView(显示评论)和底部一个LinearLayout布局组成。
第二层,是通过后台获取的子评论的数据,这一层主要是将获取的数据填充至ListView中。
第三层,评论的子评论。在第二层,每填充一个子item时,都请求一下该item下是否还有子item,如果有则通过一个新布局的LinearLayout填充数据。具体代码如下:
具体代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/includeTop"
layout="@layout/common_top"></include>
<!-- 展示评论的ListView -->
<ListView
android:id="@+id/lv_read_comment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:showDividers="beginning"
android:divider="@drawable/divide_shape"
android:orientation="vertical">
<!-- 输入框、留言按钮 -->
<LinearLayout
android:id="@+id/ll_read_comment"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/white"
android:layout_marginLeft="10dp"
android:orientation="horizontal">
<!-- 输入框 -->
<EditText
android:id="@+id/et_read_input"
android:focusable="true"
android:focusableInTouchMode="true"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:background="@color/white"
android:gravity="center_vertical"
android:inputType="textMultiLine"
android:textColor="@color/black"
android:maxLength="100"
android:maxLines="6"
android:hint="添加评论..."
android:minHeight="40dp"/>
<!-- 留言按钮 -->
<Button
android:id="@+id/btn_read_submit"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"
android:background="@drawable/date_saveset_bg"
android:text="评论"
android:textColor="#000000" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
每个子评论Item的布局,自定义布局
<?xml version="1.0" encoding="utf-8"?>
<com.c317.warmlight.android.views.CommentItemView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dip">
<ImageView
android:id="@+id/lv_comment_portrait"
android:layout_width="48dip"
android:layout_height="48dip"
android:src="@drawable/musi02" />
<View
android:layout_width="8dip"
android:layout_height="match_parent" />
<!-- 评论 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_comment_nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:textColor="#999999" />
<TextView
android:id="@+id/tv_commnet_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="none"
android:textColor="@color/black"
android:paddingBottom="5dip"
android:paddingTop="5dip" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/tv_comment_commenttime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/gray_gray" />
<ImageButton
android:id="@+id/ib_comment_morebtn"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentRight="true"
android:background="@drawable/comment_1"
android:focusable="true" />
</RelativeLayout>
<!-- 二级评论 -->
<LinearLayout
android:id="@+id/comment_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tv_comment_commenttime"
android:background="@color/white"
android:orientation="vertical"
android:padding="5dp"
android:visibility="gone"></LinearLayout>
</LinearLayout>
</com.c317.warmlight.android.views.CommentItemView>
2.数据初始化:先取一级评论数据,填充至一级评论的ListView,在填充Adapter的时候,通过id获取到二级评论,在填充二级评论;
关键代码如下:
填充一级评论数据
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView != null) {
view = convertView;
} else {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.comment_item_view, null, false);//填充ListView
}
if (view instanceof CommentItemView) {
//评论具体各部分填充数据
Comment.CommentItem item = getItem(position);
((CommentItemView) view).setData(item);
((CommentItemView) view).setPosition(position);
((CommentItemView) view).setCommentListener(this);
//根据位置,将View存起来,当下次评论时,以便取出
cacheView(position, (CommentItemView) view);
}
return view;
}
填充二级评论数据
// 获取一级评论时,在调用获取二级评论的方法
public void setData(Comment.CommentItem data) {
mData = data;
commentnickname.setText(data.userName);
commnetcontent.setText(data.comContent);
commenttime.setText(data.comTime);
updateComment(mData.commentID);//获取二级评论
commentbutton.setOnClickListener(this);
}
private void updateComment(int comID) {
commentlayout.removeAllViews();
if (mData.userName != null) {
commentlayout.setVisibility(View.VISIBLE);
//取二级评论数据
String secondComUrl = AppNetConfig.BASEURL + AppNetConfig.SEPARATOR + AppNetConfig.DATE + AppNetConfig.SEPARATOR + AppNetConfig.GETOTHERCOMMENT;
RequestParams params = new RequestParams(secondComUrl);
params.addParameter("commentID", comID);
x.http().get(params, new Callback.CommonCallback<String>() {
@Override
public void onSuccess(String result) {
.........
}
}
}
}
- 评论数据的写入,更新:
重新调用方法,重新取数据,刷新组件;
page4.gif4 上传图片功能
由于该功能涉及的知识较多,我又单独总结了一篇,重要的知识都在里面:
5 新闻内容页面动画效果
1 功能展示:
page5.gif2 功能实现
主要是通过监听触摸事件(setOnTouchListener),不过需要重写WebView的方法。
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float disY = event.getY() - lastY;
//垂直方向滑动
if (Math.abs(disY) > viewSlop) {
//设置了TextView的点击事件之后,会导致这里的disY的数值出现跳号现象,最终导致的效果就是
//下面的tool布局在手指往下滑动的时候,先显示一个,然后再隐藏,这是完全没必要的
//是否向上滑动
isUpSlide = disY < 0;
//实现底部tools的显示与隐藏
if (isUpSlide) {
if (!isToolHide)
hideTool();
} else {
if (isToolHide)
showTool();
}
}
lastY = event.getY();
break;
}
mGestureDetector.onTouchEvent(event);
重写WebView,定义在布局中
public class NewsWebView extends WebView {
private BottomListener bottomListener;
private onScrollListener scrollListener;
public NewsWebView(Context context) {
super(context);
}
public NewsWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NewsWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
super.onScrollChanged(l, t, oldl, oldt);
if (getScrollY() + getHeight() >= computeVerticalScrollRange()) {
//监听滑动到底部的事件
if (null != bottomListener) {
bottomListener.onBottom();
}
}
if (null != scrollListener) {
scrollListener.onScrollChanged(l, t, oldl, oldt);
}
}
public void setBottomListener(BottomListener bottomListener) {
this.bottomListener = bottomListener;
}
public void setScrollListener(onScrollListener scrollListener) {
this.scrollListener = scrollListener;
}
public interface onScrollListener {
public void onScrollChanged(int l, int t, int oldl, int oldt);
}
public interface BottomListener {
public void onBottom();
}
}
6 SQLite数据库
SQLite常用的操作方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//打开或创建test.db数据库
SQLiteDatabase db = openOrCreateDatabase("test.db", Context.MODE_PRIVATE, null);
db.execSQL("DROP TABLE IF EXISTS person");
//创建person表
db.execSQL("CREATE TABLE person (_id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR, age SMALLINT)");
Person person = new Person();
person.name = "john";
person.age = 30;
//插入数据
db.execSQL("INSERT INTO person VALUES (NULL, ?, ?)", new Object[]{person.name, person.age});
person.name = "david";
person.age = 33;
//ContentValues以键值对的形式存放数据
ContentValues cv = new ContentValues();
cv.put("name", person.name);
cv.put("age", person.age);
//插入ContentValues中的数据
db.insert("person", null, cv);
cv = new ContentValues();
cv.put("age", 35);
//更新数据
db.update("person", cv, "name = ?", new String[]{"john"});
Cursor c = db.rawQuery("SELECT * FROM person WHERE age >= ?", new String[]{"33"});
while (c.moveToNext()) {
int _id = c.getInt(c.getColumnIndex("_id"));
String name = c.getString(c.getColumnIndex("name"));
int age = c.getInt(c.getColumnIndex("age"));
Log.i("db", "_id=>" + _id + ", name=>" + name + ", age=>" + age);
}
c.close();
//删除数据
db.delete("person", "age < ?", new String[]{"35"});
//关闭当前数据库
db.close();
//删除test.db数据库
// deleteDatabase("test.db");
}
上面的代码中基本上囊括了大部分的数据库操作;对于添加、更新和删除来说,我们都可以使用
db.executeSQL(String sql);
db.executeSQL(String sql, Object[] bindArgs);//sql语句中使用占位符,然后第二个参数是实际的参数集
除了统一的形式之外,他们还有各自的操作方法:
db.insert(String table, String nullColumnHack, ContentValues values);
db.update(String table, Contentvalues values, String whereClause, String whereArgs);
db.delete(String table, String whereClause, String whereArgs);
丰富的查询形式
db.rawQuery(String sql, String[] selectionArgs);
db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy);
db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);
db.query(String distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);
下面是Cursor对象的常用方法:
c.move(int offset); //以当前位置为参考,移动到指定行
c.moveToFirst(); //移动到第一行
c.moveToLast(); //移动到最后一行
c.moveToPosition(int position); //移动到指定行
c.moveToPrevious(); //移动到前一行
c.moveToNext(); //移动到下一行
c.isFirst(); //是否指向第一条
c.isLast(); //是否指向最后一条
c.isBeforeFirst(); //是否指向第一条之前
c.isAfterLast(); //是否指向最后一条之后
c.isNull(int columnIndex); //指定列是否为空(列基数为0)
c.isClosed(); //游标是否已关闭
c.getCount(); //总数据项数
c.getPosition(); //返回当前游标所指向的行数
c.getColumnIndex(String columnName);//返回某列名对应的列索引值
c.getString(int columnIndex); //返回当前行指定列的值
总体来说,SQLlite就是一个数据库,功能方面和常见的数据库没有什么区别,唯一的不便就是,没有立即的可视化工具,只有使用模拟器,才可以在电脑上实际看到各表的结构和数据。
网友评论