小桔灯技术总结

2018-05-02  本文已影响12人  艾剪疏

最近,做完了第一个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布局,实现该功能。

  1. 重写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
  }
}
  1. 然后在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;
            }
        }
  1. 在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.gif

2 说明:分类展示用户发布的有约。

3 需要的轮子:

4 实现思路:
将需要展示的页面以Fragment 的形式填充到页面ViewPager中。

  1. 自定义每个显示的Item,布局即为需要展示的页面布局,继承于Fragment。将这些Fragment存入到ArrayList中;
  2. 将需要展示Item填充至Viewpager的Adapter中,设置相应的监听事件等属性;
  3. 更改Viewpager的样式,修改或重写其引用的样式文件;
  4. 覆写PullToRefreshListView的上拉和下拉刷新的方法,实现上拉下拉刷新。

3 评论功能,包括二级评论

(1)效果

comment.gif

(2)说明:书籍的评论,包括二级评论。

(3)实现:

  1. 布局:
    这一块主要的难点是布局的嵌套,主要是以下几个布局的嵌套。

(1)像主评论布局中嵌入一级评论布局,在向一级评论布局中嵌入二级评论(多个)。
(2)点击评论时,下方弹窗要动态的弹出和落下。

QQ截图20180502142033.png

首先就是一级布局,有一个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) {
                        .........
                }
        }
    }
}
  1. 评论数据的写入,更新:
    重新调用方法,重新取数据,刷新组件;

4 上传图片功能

page4.gif

由于该功能涉及的知识较多,我又单独总结了一篇,重要的知识都在里面:

Android上传图片到服务端

5 新闻内容页面动画效果

1 功能展示:

page5.gif

2 功能实现

主要是通过监听触摸事件(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就是一个数据库,功能方面和常见的数据库没有什么区别,唯一的不便就是,没有立即的可视化工具,只有使用模拟器,才可以在电脑上实际看到各表的结构和数据。

上一篇下一篇

猜你喜欢

热点阅读