Android-UI 慕淘旅游

项目说明

慕淘旅游是对UI常用组件的一次集合使用,其中包括Activity和Fragment之间的传值与调用、不同适配器的设置以及ListView、RecycleView等控件的用法等等

成品展示

Activity & Fragment

Activity的代码量不大,仅用于完成简单的布局和页面跳转功能,主要的布局都在Fragment

Activity

  • 打开软件的欢迎页WelcomeActivity
  • 存储显示底部导航按钮MainActivity
  • 登录页面LoginActivity
  • 注册页面RegisterActivity

Fragment

  • 主页IndexFragment
  • 发现页FindFragment
  • 我的MeFragment

WelcomeActivity

欢迎页就是打开APP见到的第一个页面,布局就是一个背景图片加一个logo
在这里插入图片描述
打开APP后将会在欢迎页停留3秒,3秒后自动跳转到主页
这里是通过控制线程的休眠来实现停留3秒后跳转的

代码

	@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_welcome);

        //通过线程实现进入欢迎页面后等待3秒自动跳转到主页面
        new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    //休眠3秒
                    sleep(3000);
                    //跳转
                    startActivity(new Intent(WelcomeActivity.this, MainActivity.class));
                    finish();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

要记得在Manifest文件里面将WelcomeActivity设为初始页面
这里有个小知识点,下面这个图片的标题栏是不是很碍眼
在这里插入图片描述
同样在Manifest文件中在每个Activity里面加上下面这段代码修改一下样式就能去掉了

android:theme="@style/Theme.AppCompat.Light.NoActionBar"

MainActivity

每个页面都放到了Fragment里,切换的时候只需要显示不同的Fragment就可以了,所以对于主页的布局只有一个导航栏
打开APP后通过底部三个按钮完成页面切换
在这里插入图片描述
这篇布局文件太多太复杂,简单的就不贴了,这三个按钮用的是单选控件RadioButton
相似度比较高的属性可以摆到styles.xml文件里,然后通过style="@style/stylename"实现调用,可以大量减少重复代码

代码

OnCreate方法中

	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        index = new IndexFragment();
        find = new FindFragment();
        me = new MeFragment();

        //实例化FragmentTransaction,实现Fragment的动态添加
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.add(R.id.container, index);
        transaction.commit();
    }

为底部导航栏按钮添加点击方法myClick

	public void myClick(View v) {
        //底部导航栏单击事件
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        switch (v.getId()) {
            case R.id.index:
                transaction.replace(R.id.container, index);
                break;
            case R.id.find:
                transaction.replace(R.id.container, find);
                break;
            case R.id.me:
                transaction.replace(R.id.container, me);
                break;
        }
        transaction.commit();
    }

LoginActivity

登录界面比较简单,这里并没有实现登录功能只是做了简单的UI
在这里插入图片描述

代码

点击右上角的X结束当前界面

	//点击叉号图标退出当前界面
    findViewById(R.id.back_close).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            finish();
        }
    });

点击登录按钮下放的免费注册文本跳转至注册界面

    //点击立即注册文本跳转至注册界面
    findViewById(R.id.register_txt).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            startActivity(new Intent(LoginActivity.this, RegisterActivity.class));
            finish();
        }
    });

RegisterActivity

注册界面和登录界面差不多,这里也只实现UI,并未实现注册功能
在这里插入图片描述

代码

点击右上方直接登录文本跳转回登录界面

    //点击直接登录文本跳转至登录界面
    findViewById(R.id.login_txt).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            startActivity(new Intent(RegisterActivity.this, LoginActivity.class));
            finish();
        }
    });

上面四个Activity都比较简单,重点在下面首页、发现页和我的三个Fragment当中
我单独创建了一个DataUtil类,这个类主要是用来存放各个模块所需的图片、文字,方便后面的修改


MeFragment

我的页面是三个页面里相对较为简单的页面
在这里插入图片描述
上方是登录按钮,点击后可以跳转至登录界面,下方是ListView控件可以上下滚动显示

代码

登录跳转和之前的跳转没什么差别,唯一要注意的是在Fragment中的环境上下文不再是xxx.this而是通过getContext()方法获取
之前提到的列表是通过实例化SimpleAdapter适配器来实现的
先在布局文件里面创建一个合适的ListView

    <ListView
        android:id="@+id/me_listview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:divider="@null"/>

这里通过设置最后一行属性,将ListView原本的分界线隐藏
使用SimpleAdapter来完成布局还需要创建一个布局文件,在这个文件里实现单个布局,最后可以根据数据的数量来批量实现,这里的me_menu_listview就是我创建的布局文件
首先初始化数据

    private void initData() {
        //将数据添加到data中
        for (int i = 0; i < DataUtil.me_imgs.length; i++) {
            Map<String, Object> map = new HashMap<>();
            map.put("img", DataUtil.me_imgs[i]);
            map.put("txt", DataUtil.me_txts[i]);
            data.add(map);
        }
    }

实例化SimpleAdapter

	String[] from = {"img", "txt"};
    int[] to = {R.id.image_listview, R.id.text_listview};
    //在Fragment中Context必须通过getContext()来获取
    //参数1:环境上下文  参数2:要添加的数据源  参数3:布局文件  参数4和参数5:将数据源对应数据添加到对应的布局中
    SimpleAdapter adapter = new SimpleAdapter(getContext(), data, R.layout.me_menu_listview, from, to);

    meListView.setAdapter(adapter);

IndexFragment

主页主要分为两大块,一块是上方的广告轮播,这里需要用到ViewPager2控件来实现,另一块就是广告轮播下方的所有内容

广告轮播

感觉这里用帧布局做总体布局会好一些,上面的扫一扫、消息还有输入框都挺简单的,难点集中在中间的广告轮播功能,这里需要添加ViewPager2控件

    <!--广告轮播-->
    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pagers_top"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

IndexFragment中将广告轮播的实现代码写在setViewPager()
在这个方法里需要实例化一个RecyclerView.Adapter适配器
这里是将广告图片作为视图的背景来进行展示,这样的话只需要创建一个空的线性布局adv_item即可

	//实现图片滑动轮播
    RecyclerView.Adapter adapter = new RecyclerView.Adapter() {

        //用于获取盛放图片的控件的ViewHolder对象
        @NonNull
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            //创建一个空白布局,将轮播的图片设置为布局的背景图片
            View v = LayoutInflater.from(getContext()).inflate(R.layout.adv_item, parent, false);
            return new ViewHolder(v);
        }

        //为ViewHolder对象中的控件设置显示效果
        @Override
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
            ViewHolder vh = (ViewHolder) holder;
            vh.v.setBackgroundResource(DataUtil.adv_imgs[position % 4]);
        }

        //设置pager的数量,这里设置为Integer的最大值,这样可以一直往右划,不用到最后又返回第一张开始滚动
        @Override
        public int getItemCount() {
            return Integer.MAX_VALUE;
        }
    };
    
    pagers_top.setAdapter(adapter);

这里返回的是ViewHolder对象,但本身的ViewHolder需要实现其它方法,并不是我想要的,所以自己写一个ViewHolder类来继承这个RecycleView.Adapter

    class ViewHolder extends RecyclerView.ViewHolder {
        public View v;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            v = itemView;
        }
    }
    

完成上面两步就可以实现手动滑动了
下面实现的是图片和它对应指示器的绑定,实现在滑动图片时,下方的指示器也跟着滚动

    //滑动事件:同步指示器、修改自动轮播位置
    pagers_top.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
        @Override
        public void onPageSelected(int position) {
            super.onPageSelected(position);
            for (int i = 0; i < pointers.getChildCount(); i++) {
                //先将所有指示器设置为未选中状态
                ImageView img = (ImageView) pointers.getChildAt(i);
                img.setImageResource(R.drawable.dot_unselected);
            }
            //position位置对应的指示器设置为选中状态
            ((ImageView) pointers.getChildAt(position % 4)).setImageResource(R.drawable.dot_selected);
            //将index设置为当前位置
            index = position;
        }
    });

在这里插入图片描述 在这里插入图片描述
到这里就实现了广告手动轮播,最后加入线程,通过线程实现自动轮播,并设置每张图片显示的时间,这里显示间隔设置为3秒

  	//通过线程来实现自动轮播
    new Thread() {
        @Override
        public void run() {
            super.run();
            while (true) {
                try {
                    sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                pagers_top.setCurrentItem(index);
                index++;
            }
        }
    }.start();

主页的其他组件

因为内容较多,所以需要用到ScrollView滚动条,但是这里要注意ScrollView内部只能有一个子控件,所以在这个控件里面的子控件都添加到一个线性布局中
这里的难点在于一级菜单
在这里插入图片描述
还有二级菜单,这里二级菜单是可以向右滚动的,有点类似于广告轮播
在这里插入图片描述 在这里插入图片描述

一级菜单

一级菜单是通过GridView控件和SimpleAdapter适配器来实现
GridView控件

	<!--一级菜单-->
    <GridView
        android:id="@+id/index_grids"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        android:numColumns="4" />

SimpleAdapter适配器
先初始化数据,这里的图片和文本我都放到了DataUtil类里面

    private List<Map<String, Object>> data = new ArrayList<>();
  	//一级菜单数据
    for (int i = 0; i < DataUtil.index_menu1_imgs.length; i++) {
        Map<String, Object> map = new HashMap<>();
        map.put("img", DataUtil.index_menu1_imgs[i]);
        map.put("txt", DataUtil.index_menu1_txts[i]);
        data.add(map);
    }

然后实例化SimpleAdapter就完成添加了

    //设置一级菜单
    private void setGridView() {

        String[] from = {"img", "txt"};
        int[] to = {R.id.index_image_grid, R.id.index_text_grid};
        SimpleAdapter adapter = new SimpleAdapter(getContext(), data, R.layout.index_menu_grid, from, to);
        grids.setAdapter(adapter);
    }

二级菜单

二级菜单比一级菜单实现起来更复杂一点,它是通过RecycleView控件和RecyclerView.Adapter适配器来实现
先初始化数据,这里用的data跟一级菜单是同一个,因为是键值对数据,所以不用担心混淆

    //设置二级菜单数据
    private void setMenuDate() {
        //将数据添加至data中
        for (int i = 0; i < DataUtil.index_menu2_imgs.length; i++) {
            Map<String, Object> map = new HashMap<>();
            map.put("img", DataUtil.index_menu2_imgs[i]);
            map.put("txt", DataUtil.index_menu2_txts[i]);
            data.add(map);
        }
    }

然后实例化RecycleView.Adapter完成添加

	//二级菜单
    private void setMenuPager() {
    	// 创建视图管理器,设置为水平布局
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);

        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {

            @NonNull
            @Override
            public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
                View v = LayoutInflater.from(getContext()).inflate(R.layout.menu_item, parent, false);
                return new ViewHolder(v);
            }

            @Override
            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
                ViewHolder vh = (ViewHolder) holder;
                vh.img1.setImageResource(DataUtil.index_menu2_imgs[0]);
                vh.txt1.setText(DataUtil.index_menu2_txts[0]);
                vh.img2.setImageResource(DataUtil.index_menu2_imgs[1]);
                vh.txt2.setText(DataUtil.index_menu2_txts[1]);
                vh.img3.setImageResource(DataUtil.index_menu2_imgs[2]);
                vh.txt3.setText(DataUtil.index_menu2_txts[2]);
            }

            @Override
            public int getItemCount() {
                return Integer.MAX_VALUE;
            }
        };

        recyclerView.setAdapter(adapter);
    }

同样这里的ViewHolder需要重写一下,因为做广告轮播的时候重写过一次,这次直接在那上面修改就可以了

    class ViewHolder extends RecyclerView.ViewHolder {
        public View v;
        public ImageView img1, img2, img3;
        public TextView txt1, txt2, txt3;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            v = itemView;
            img1 = itemView.findViewById(R.id.image1_menu2);
            txt1 = itemView.findViewById(R.id.text1_menu2);
            img2 = itemView.findViewById(R.id.image2_menu2);
            txt2 = itemView.findViewById(R.id.text2_menu2);
            img3 = itemView.findViewById(R.id.image3_menu2);
            txt3 = itemView.findViewById(R.id.text3_menu2);
        }
    }

到这里一级菜单和二级菜单已经完成了,剩下的就是一些比较简单的布局

FindFragment

最后发现页也需要一个ScrollView来完成页面滚动
在这里插入图片描述
这里的一级菜单和二级菜单用的和主页的一级菜单一样是通过GridViewSimpleAdapter来实现的,中间的旅行PK模块用帧布局来实现,其它都是一些简单的线性布局和相对布局结合实现的
这里的难点在于热门头条下面的三行咨询的显示
在这里插入图片描述
这里是通过ListView控件和BaseAdapter自定义适配器来实现

代码

ListView控件

    <ListView
        android:id="@+id/find_listview"
        android:layout_width="match_parent"
        android:layout_height="330dp"
        android:divider="@null"/>

实例化BaseAdapter适配器
这里的Find_Hot_Msg是新建的一个类,用来存储文字以及图片数据

    // 热门头条块ListView
    private void setListView() {
        // 通过自定义适配器BaseAdapter完成
        BaseAdapter adapter = new BaseAdapter() {
            // 获取数量(设置ListView的长度)
            @Override
            public int getCount() {
                return list.size();
            }

            // 获取视图(设置ListView每一项的显示效果)
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                ViewHolder holder;
                //完成对convertView的设置
                /// 将布局资源转为view
                if (convertView == null) {
                    //优化:利用进入RecycleBin中的View,减少view的重复赋值
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.find_hot_listview, null);
                    holder = new ViewHolder();
                    holder.title = convertView.findViewById(R.id.listview_title);
                    holder.from = convertView.findViewById(R.id.listview_from);
                    holder.eye = convertView.findViewById(R.id.listview_eyenum);
                    holder.like = convertView.findViewById(R.id.listview_likenum);
                    holder.img = convertView.findViewById(R.id.listview_img);
                    convertView.setTag(holder);
                } else {
                    // 优化:
                    // 通过getTag()取出ViewHolder对象,然后能够直接通过holder.控件的方式在外面直接操作控件
                    // 从而避免了大幅度使用findViewById操作
                    // getTag()操作效率比findViewById要高
                    holder = (ViewHolder) convertView.getTag();
                }
                Find_Hot_Msg fhm = list.get(position);
                //标题title
                holder.title.setText(fhm.getTitle());
                //来源from
                holder.from.setText(fhm.getFrom());
                //阅读数量eye
                holder.eye.setText(fhm.getEye());
                //点赞数量like
                holder.like.setText(fhm.getLike());
                //图片img
                holder.img.setImageResource(fhm.getPic());

                return convertView;
            }

            // ==============两个酱油方法
            @Override
            public Object getItem(int position) {
                return null;
            }

            @Override
            public long getItemId(int position) {
                return 0;
            }
        };
        listView.setAdapter(adapter);
    }

这里同样要重写ViewHolder

	// 优化:
    // 当view为null时,完成对ViewHolder的实例化工作,并为各个控件属性赋值
    // 性能的提升是在view不为null时体现的
    // 当view为null时,完成了ViewHolder及内部控件属性的初始化工作后,调用一句代码:view.setTag(holder);
    // 当view不为null时,holder = view.getTag();
    static class ViewHolder {
        TextView title, from, eye, like;
        ImageView img;
    }

但是这里会出现一个问题,就是ListView控件是自带一个滚动条的,它和ScrollView只能同时存在一个,不然会发生冲突导致ListView滚动条失效
这里我暂时想到三个解决的办法:

  1. 将每条内容分开放到单独的LinearLayout中,直接作为线性布局来摆放
  2. 如果数据量不大的话,比如像这个发现页只有三行,可以直接将这个ListView控件的高度写死,我就是用的这种方法
  3. 写一个新的MyListView类来继承ListView这个类,重写里面的方法,修改当ListView滑动时ScrollView的拦截事件

下面是实现第三个解决办法的代码:

public class MyListView extends ListView {
    //重写构造
    public MyListView(Context context) {
        this(context, null);
    }

    public MyListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev);
    }


    //为listview/Y,设置初始值,默认为0.0(ListView条目一位置)
    private float mLastY;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        //重点在这里
        int action = ev.getAction();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                super.onInterceptTouchEvent(ev);
                //不允许上层viewGroup拦截事件.
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                //满足listView滑动到顶部,如果继续下滑,那就让scrollView拦截事件
                if (getFirstVisiblePosition() == 0 && (ev.getY() - mLastY) > 0) {
                    //允许上层viewGroup拦截事件
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                //满足listView滑动到底部,如果继续上滑,那就让scrollView拦截事件
                else if (getLastVisiblePosition() == getCount() - 1 && (ev.getY() - mLastY) < 0) {
                    //允许上层viewGroup拦截事件
                    getParent().requestDisallowInterceptTouchEvent(false);
                } else {
                    //不允许上层viewGroup拦截事件
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_UP:
                //不允许上层viewGroup拦截事件
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
        }

        mLastY = ev.getY();
        return super.dispatchTouchEvent(ev);

    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(ev);
    }
}

完成这个类之后在布局文件中将ListView控件替换成MyListView再设置一些参数就可以了


源码链接: https://download.csdn.net/download/lckgr/12616257

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值