最近在实习,在此期间实现一个仿制版的脸萌app,仅十天完成开发,现在总结一下这十天的工作。
首先看看此项目三个主要界面
1、启动界面设置无title栏,然后在xml布局中描述界面即可,很简单。
2、登录界面,盖界面涉及到样式选择器,使按钮点击产生‘反馈’效果。左上角声音开关,控制声音。中央区域主要由Button构成,该界面button多数添加动画效果。我们在util包下定义好动画工具类,然后直接在界面中调用即可,其中主要涉及平移,旋转,透明度变化,缩放动画。其中在旋转动画中,可以设置坐标的相对类型,是绝对型,还是相对父容器的相对坐标还是相对自身的相对坐标。
3、主游戏界面的设计。祝游戏界面可分为三块,第一块是标题栏,包含了首页,保存,分享啦。第二块是自定义view区域,主要用于由用户自己设计一个人物卡通形象出来。第三块是viewpager构成的各种属性选择栏,可以选择发型啦,脸型啦,眉毛等等。其中第二,第三块是该项目的核心部分。
首先来说说第三部分的viewpager,此viewpager承载的Fragment,所以viewpager的适配器我们采用的是自己定义的继承自FragmentPagerAdapter的类(Fragmentadapter),此适配器主要功能在GetItem方法,用于给viewpager提供fragment用于显示。这儿getItem返回的fragment是自定义的pagerfragment,继承自fragment。这个pagerfragment才是真正的显示给用户看到的那个选择什么发型那个界面,其中这个pagerfragment是包含一个gridview。gridview也叫宫格布局,宫格布局也需要一个适配器给他提供数据,这儿的数据由当前viewpager的index来确定,也就是当前是viewpager的第几页,也就是当前用户选择的是发型还是脸型,还是眉毛。获取到资源的id数组后,经过适当处理我们就能将viewpager做出来了。这时候发型,脸型,眉毛的那个水平滑动栏还没实现。这个滑动栏是HorizontalScrollView实现的,HorizontalScrollView有一个特点就是里面只能承载一个布局,所以我们放一个radiogroup,在radiogroup里面放若干个radiobutton,用于显示发型,脸型,眉毛啊等等。此时滑动栏还没和viewpager结合起来呢。。。好吧,下面的这一步挺重要的。哦,对了,在当前选中属性,日入发型,下面会有一条蓝色的横线,代表当前选中的属性。这个也要结合滑动栏和viewpager一起实现。嗯,让我们理清一下操作,用户滑动viewpager切换当前属性,比如从发型切换到脸型了,此时发型下面的蓝线也要跟着跑到脸型下面去,还有用户可以直接在滑动栏选择属性,比如直接点击选择眉毛,那么viewpager就要显示所有可以选择的眉毛图样。好了,总体操作说完了,下面谈谈实现。
/** * 初试化viewpager */
@SuppressWarnings("deprecation")
private void initPager() {
// 获取控件
viewPager = (ViewPager) findViewById(R.id.viewpager);
FragmentAdapter fragmentAdapter = new FragmentAdapter(
getSupportFragmentManager(),sex,mainView);
// 绑定适配器
viewPager.setAdapter(fragmentAdapter);
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
// position表示当前页面的序号
/** * positionOffset 当前滑动比例 * 如果从左往右划,那么offset从0到1(翻页后变为0).若由右向左划,那么offset由1到0 */
// Log.d("onPageScrolled",
// "pos="+position+" offset="+positionOffset);
int radioBtWidth = radioButton.getWidth();
int newposition = (int) (position * radioBtWidth + positionOffset
* radioBtWidth);
int center = (viewPager.getWidth() - radioBtWidth) / 2;
hsv.scrollTo(newposition - center, 0);
// Log.d("onPageScrolled",
// "newpos="+newposition+" scrollto="+(newposition-center));
startMoveCursor(position, positionOffset);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
private int from = 0;
/** * 控制游标移动 * * @param position * 当前移动的位置 * @param positionOffset * 当前移动的百分比 */
protected void startMoveCursor(int position, float positionOffset) {
// 获取当前被选中的radiobutton
RadioButton rb = (RadioButton) radioGroup.getChildAt(position);
// 定义两个长度的数组 0下标 代表x坐标 1下标y坐标
int[] location = new int[2];
// 获取当前radiobutton的坐标
rb.getLocationInWindow(location);
// 计算移动后的坐标
int to = (int) (location[0] + positionOffset * rb.getWidth());
// 创建动画
TranslateAnimation ta = new TranslateAnimation(from, to, 0, 0);
ta.setDuration(100);
// 动画完成以后停留在当前结束的位置
ta.setFillAfter(true);
cursor.startAnimation(ta);
from = to;
}
/** * 初试化tab选择栏 */
private void initTab() {
radioGroup = (RadioGroup) findViewById(R.id.radiogroup);
radioButton = (RadioButton) findViewById(R.id.rb1);
radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.rb1:
viewPager.setCurrentItem(0);
break;
case R.id.rb2:
viewPager.setCurrentItem(1);
break;
case R.id.rb3:
viewPager.setCurrentItem(2);
break;
case R.id.rb4:
viewPager.setCurrentItem(3);
break;
case R.id.rb5:
viewPager.setCurrentItem(4);
break;
case R.id.rb6:
viewPager.setCurrentItem(5);
break;
case R.id.rb7:
viewPager.setCurrentItem(6);
break;
case R.id.rb8:
viewPager.setCurrentItem(7);
break;
case R.id.rb9:
viewPager.setCurrentItem(8);
break;
case R.id.rb10:
viewPager.setCurrentItem(9);
break;
case R.id.rb11:
viewPager.setCurrentItem(10);
break;
default:
break;
}
}
});
}
上面直接将相关代码贴出来了,在initTab方法,我们初始化了水平滑动栏,并监听了用户点击事件,实现viewpager的切换。在initPager方法我们实现了pager的功能,之前说过了这个pager的构成,需要适配器啊,适配器返回fragment,这个fragment有需要一个适配器提供数据源啦。。。这里不过多说了,好了,viewpager显示出来了,那么我们要将滑动栏viewpager,蓝色选中线(游标)结合起来,就需要一点计算了,我们在滑动viewpager时候,水平滑动栏也要动,他们是相对应的,那么我们需要计算viewpager被滑动的距离(比例),将滑动栏的显示随着viewpager的滑动变就可以了,好了下面还有游标的滑动,游标一直指向当前选中的属性,比如发型。那么游标就是个TextView,它的变化就是平移动画,同样的我们知道了viewpager的滑动比例,我们就可以计算游标移动的位置。
4、接下来我们讲讲最后一部分重难点,就是卡通人物形象的绘制,自定义view的实现。首先,我们先定义一个图片资源工具类,用于获取所有男孩发型图片资源,女孩发型图片资源等等。在刚app时候,我们先给用户加载一套默认的卡通人物形象。第一步,获取所要展现的图片的id,第二部,将这些id转化成bitmap,第三部图片缩放处理,第四部图片按照一定顺序绘制在指定位置形成一个完成的卡通人物形象。好了,默认的卡通显示出来了,那么用户点击了发型,换一个发型,我们怎么将新换的发型更换呢。这儿设计到java编程黎很重要的一个概念叫做回调机制。具体回调机制怎么定义可以自行百度,在这儿我只说说我的理解。在之前,我们都写过时间监听,监听实现接口或者匿名内部类就是回调机制最好的例证。回调机制可用于不同的类之间传递参数,应该说当某时刻触发某件事时候,才去向指向类传递参数可以用回调实现。回调需要定义回调的接口,接口的实现类就是需要接受回调参数的类,触发类是接口类调用接口方法的类。在触发类里面需要传入接口实现类的对象用于触发回调接口方法。这里参数为什么会从触发类传递到接口实现类呢,其实很简单。在触发类里,接口对象调用了接口的方法,接口方法必须去找实现接口的类去实现方法的调用,接受参数类就是接口实现类,自然参数就在实现类里面被调用了,是不是很巧妙。
/** * 用户点击人物装饰,通知mainview重绘接口 * @author Mr.Euler * */
public interface IDecorationChangedListener {
/** * * @param index 图片更换的图层(哪个部位) * @param resId 更换图片资源id */
public void onchanged(int index,int resId);
}
这个就是我定义的回调接口
/** * * @author Mr.Euler * */
public class MainView extends View implements IDecorationChangedListener{
private int ids[];
private Bitmap bitmaps[];
private boolean sex;
private int view_width, view_height;
public MainView(Context context) {
super(context);
}
public MainView(Context context, boolean sex) {
super(context);
this.sex = sex;
initBitmaps();
//设置允许获取view,用于保存图片
this.setDrawingCacheEnabled(true);
}
@Override
public void onchanged(int index, int resId) {
ids[index]=resId;
//重绘
invalidate();
}
上面是接口实现类也就是需要接收参数的类,mainview实现接口,那么就得实现接口方法,在接口方法中接受参数,实现重绘。这儿讲讲点击发型怎么换发型,首先我们先要获取当前viewpager的index也就是指导用户点的是哪个属性,是发型,脸型还是眉毛等等,还要获取所点击的图片是哪张,获取图片的资源id。ok,将index和id传递给mainview,通过回调传过去,mainview就知道是什么类型的图,被换成了什么图了。最后重绘,更新卡通人物。
Everyday can make a difference
源代码下载地址github源码地址
今天的文章安卓app——脸萌分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/61418.html