美文网首页
小桔灯技术总结

小桔灯技术总结

作者: 艾剪疏 | 来源:发表于2018-05-02 14:34 被阅读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 需要的轮子:

    • Viewpager :控制页面切换
    • PullToRefreshListView :控制刷新

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

    相关文章

      网友评论

          本文标题:小桔灯技术总结

          本文链接:https://www.haomeiwen.com/subject/azvjkftx.html